You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2020/03/25 20:07:12 UTC

[airavata-django-portal] branch develop updated (ea01ec8 -> 91e4318)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


    from ea01ec8  Lint: fix import sort order
     new 97975d8  AIRAVATA-3126 UI for managing reservations
     new ac818b4  AIRAVATA-3216 ComputeResourceReservation validations
     new 1a4b756  AIRAVATA-3126 Add support for deleting all expired reservations
     new 6f9ed0d  AIRAVATA-3216: Remove dynamic sorting, but sort new reservations by start time
     new 4afb2c9  AIRAVATA-3126 remove parts of hour from reservation default start/end time
     new f7aed1c  AIRAVATA-3216 Don't allow picking an end time before start time
     new b54d480  AIRAVATA-3126 Fix save/cancel to bottom; summarize reservations
     new f322b37  AIRAVATA-3126 reservation badges
     new 91e4318  Merge branch 'AIRAVATA-3126--Implement-compute-resource-reservation-addition' into develop

The 9 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../appcatalog/groupresourceprofile/ttypes.py      | 217 +++++++++++++++---
 .../ComputePreference.vue                          | 119 +++++++---
 .../ComputeResourceReservationEditor.vue           | 158 +++++++++++++
 .../ComputeResourceReservationList.vue             | 251 +++++++++++++++++++++
 .../ComputeResourceReservationsSummary.vue         |  45 ++++
 .../GroupComputeResourcePreference.vue             | 108 ++++++---
 .../gatewayprofile/StoragePreferenceList.vue       |   2 +-
 django_airavata/apps/api/serializers.py            |   9 +-
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../django_airavata_api/js/models/BaseModel.js     | 229 ++++++++++---------
 .../js/models/ComputeResourceReservation.js        |  65 ++++++
 .../js/models/GroupComputeResourcePreference.js    |   8 +-
 django_airavata/settings_local.py.ide              |   6 +-
 .../static/common/js/components/DeleteButton.vue   |   6 +-
 .../static/common/js/components/HumanDate.vue      |   2 +-
 .../static/common/js/layouts/ListLayout.vue        |   2 +
 django_airavata/static/common/js/utils.js          |  25 +-
 17 files changed, 1047 insertions(+), 207 deletions(-)
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationsSummary.vue
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js


[airavata-django-portal] 08/09: AIRAVATA-3126 reservation badges

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit f322b3765177da2605fa559581ee27bfabb33689
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Mar 21 12:31:35 2020 -0400

    AIRAVATA-3126 reservation badges
---
 .../admin/group_resource_preferences/ComputeResourceReservationList.vue | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
index 9054f9c..757efb8 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
@@ -55,6 +55,8 @@
         <template slot="reservationName" slot-scope="data">
           {{ data.value }}
           <b-badge v-if="data.item.isExpired">Expired</b-badge>
+          <b-badge v-if="data.item.isActive" variant="success">Active</b-badge>
+          <b-badge v-if="data.item.isUpcoming" variant="info">Upcoming</b-badge>
         </template>
         <template slot="queueNames" slot-scope="data">
           <ul v-for="queueName in data.item.queueNames" :key="queueName">


[airavata-django-portal] 01/09: AIRAVATA-3126 UI for managing reservations

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 97975d8318b51086d9bff77a3977af36c6c8e0db
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sun Mar 8 15:41:13 2020 -0400

    AIRAVATA-3126 UI for managing reservations
---
 .../appcatalog/groupresourceprofile/ttypes.py      | 217 +++++++++++++++++----
 airavata/model/sharing/ttypes.py                   |  15 +-
 .../service/profile/user/cpi/UserProfileService.py |   8 +
 .../ComputePreference.vue                          | 102 +++++++---
 .../ComputeResourceReservationEditor.vue           | 139 +++++++++++++
 .../ComputeResourceReservationList.vue             | 189 ++++++++++++++++++
 .../gatewayprofile/StoragePreferenceList.vue       |   2 +-
 django_airavata/apps/api/serializers.py            |   9 +-
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../js/models/ComputeResourceReservation.js        |  41 ++++
 .../js/models/GroupComputeResourcePreference.js    |   8 +-
 django_airavata/settings_local.py.ide              |   6 +-
 .../static/common/js/components/HumanDate.vue      |   2 +-
 django_airavata/static/common/js/utils.js          |  25 ++-
 14 files changed, 689 insertions(+), 76 deletions(-)

diff --git a/airavata/model/appcatalog/groupresourceprofile/ttypes.py b/airavata/model/appcatalog/groupresourceprofile/ttypes.py
index e2841da..d5b8e18 100644
--- a/airavata/model/appcatalog/groupresourceprofile/ttypes.py
+++ b/airavata/model/appcatalog/groupresourceprofile/ttypes.py
@@ -118,6 +118,132 @@ class GroupAccountSSHProvisionerConfig(object):
         return not (self == other)
 
 
+class ComputeResourceReservation(object):
+    """
+    Attributes:
+     - reservationId
+     - reservationName
+     - queueNames
+     - startTime
+     - endTime
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRING, 'reservationId', 'UTF8', "DO_NOT_SET_AT_CLIENTS", ),  # 1
+        (2, TType.STRING, 'reservationName', 'UTF8', None, ),  # 2
+        (3, TType.LIST, 'queueNames', (TType.STRING, 'UTF8', False), None, ),  # 3
+        (4, TType.I64, 'startTime', None, None, ),  # 4
+        (5, TType.I64, 'endTime', None, None, ),  # 5
+    )
+
+    def __init__(self, reservationId=thrift_spec[1][4], reservationName=None, queueNames=None, startTime=None, endTime=None,):
+        self.reservationId = reservationId
+        self.reservationName = reservationName
+        self.queueNames = queueNames
+        self.startTime = startTime
+        self.endTime = endTime
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRING:
+                    self.reservationId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.reservationName = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 3:
+                if ftype == TType.LIST:
+                    self.queueNames = []
+                    (_etype3, _size0) = iprot.readListBegin()
+                    for _i4 in range(_size0):
+                        _elem5 = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                        self.queueNames.append(_elem5)
+                    iprot.readListEnd()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 4:
+                if ftype == TType.I64:
+                    self.startTime = iprot.readI64()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 5:
+                if ftype == TType.I64:
+                    self.endTime = iprot.readI64()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('ComputeResourceReservation')
+        if self.reservationId is not None:
+            oprot.writeFieldBegin('reservationId', TType.STRING, 1)
+            oprot.writeString(self.reservationId.encode('utf-8') if sys.version_info[0] == 2 else self.reservationId)
+            oprot.writeFieldEnd()
+        if self.reservationName is not None:
+            oprot.writeFieldBegin('reservationName', TType.STRING, 2)
+            oprot.writeString(self.reservationName.encode('utf-8') if sys.version_info[0] == 2 else self.reservationName)
+            oprot.writeFieldEnd()
+        if self.queueNames is not None:
+            oprot.writeFieldBegin('queueNames', TType.LIST, 3)
+            oprot.writeListBegin(TType.STRING, len(self.queueNames))
+            for iter6 in self.queueNames:
+                oprot.writeString(iter6.encode('utf-8') if sys.version_info[0] == 2 else iter6)
+            oprot.writeListEnd()
+            oprot.writeFieldEnd()
+        if self.startTime is not None:
+            oprot.writeFieldBegin('startTime', TType.I64, 4)
+            oprot.writeI64(self.startTime)
+            oprot.writeFieldEnd()
+        if self.endTime is not None:
+            oprot.writeFieldBegin('endTime', TType.I64, 5)
+            oprot.writeI64(self.endTime)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.reservationId is None:
+            raise TProtocolException(message='Required field reservationId is unset!')
+        if self.reservationName is None:
+            raise TProtocolException(message='Required field reservationName is unset!')
+        if self.queueNames is None:
+            raise TProtocolException(message='Required field queueNames is unset!')
+        if self.startTime is None:
+            raise TProtocolException(message='Required field startTime is unset!')
+        if self.endTime is None:
+            raise TProtocolException(message='Required field endTime is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
 class GroupComputeResourcePreference(object):
     """
     Attributes:
@@ -139,6 +265,7 @@ class GroupComputeResourcePreference(object):
      - sshAccountProvisioner
      - groupSSHAccountProvisionerConfigs
      - sshAccountProvisionerAdditionalInfo
+     - reservations
     """
 
     thrift_spec = (
@@ -161,9 +288,10 @@ class GroupComputeResourcePreference(object):
         (16, TType.STRING, 'sshAccountProvisioner', 'UTF8', None, ),  # 16
         (17, TType.LIST, 'groupSSHAccountProvisionerConfigs', (TType.STRUCT, (GroupAccountSSHProvisionerConfig, GroupAccountSSHProvisionerConfig.thrift_spec), False), None, ),  # 17
         (18, TType.STRING, 'sshAccountProvisionerAdditionalInfo', 'UTF8', None, ),  # 18
+        (19, TType.LIST, 'reservations', (TType.STRUCT, (ComputeResourceReservation, ComputeResourceReservation.thrift_spec), False), None, ),  # 19
     )
 
-    def __init__(self, computeResourceId=None, groupResourceProfileId=thrift_spec[2][4], overridebyAiravata=thrift_spec[3][4], loginUserName=None, preferredJobSubmissionProtocol=None, preferredDataMovementProtocol=None, preferredBatchQueue=None, scratchLocation=None, allocationProjectNumber=None, resourceSpecificCredentialStoreToken=None, usageReportingGatewayId=None, qualityOfService=None, reservation=None, reservationStartTime=None, reservationEndTime=None, sshAccountProvisioner=None,  [...]
+    def __init__(self, computeResourceId=None, groupResourceProfileId=thrift_spec[2][4], overridebyAiravata=thrift_spec[3][4], loginUserName=None, preferredJobSubmissionProtocol=None, preferredDataMovementProtocol=None, preferredBatchQueue=None, scratchLocation=None, allocationProjectNumber=None, resourceSpecificCredentialStoreToken=None, usageReportingGatewayId=None, qualityOfService=None, reservation=None, reservationStartTime=None, reservationEndTime=None, sshAccountProvisioner=None,  [...]
         self.computeResourceId = computeResourceId
         self.groupResourceProfileId = groupResourceProfileId
         self.overridebyAiravata = overridebyAiravata
@@ -182,6 +310,7 @@ class GroupComputeResourcePreference(object):
         self.sshAccountProvisioner = sshAccountProvisioner
         self.groupSSHAccountProvisionerConfigs = groupSSHAccountProvisionerConfigs
         self.sshAccountProvisionerAdditionalInfo = sshAccountProvisionerAdditionalInfo
+        self.reservations = reservations
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -275,11 +404,11 @@ class GroupComputeResourcePreference(object):
             elif fid == 17:
                 if ftype == TType.LIST:
                     self.groupSSHAccountProvisionerConfigs = []
-                    (_etype3, _size0) = iprot.readListBegin()
-                    for _i4 in range(_size0):
-                        _elem5 = GroupAccountSSHProvisionerConfig()
-                        _elem5.read(iprot)
-                        self.groupSSHAccountProvisionerConfigs.append(_elem5)
+                    (_etype10, _size7) = iprot.readListBegin()
+                    for _i11 in range(_size7):
+                        _elem12 = GroupAccountSSHProvisionerConfig()
+                        _elem12.read(iprot)
+                        self.groupSSHAccountProvisionerConfigs.append(_elem12)
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
@@ -288,6 +417,17 @@ class GroupComputeResourcePreference(object):
                     self.sshAccountProvisionerAdditionalInfo = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
                 else:
                     iprot.skip(ftype)
+            elif fid == 19:
+                if ftype == TType.LIST:
+                    self.reservations = []
+                    (_etype16, _size13) = iprot.readListBegin()
+                    for _i17 in range(_size13):
+                        _elem18 = ComputeResourceReservation()
+                        _elem18.read(iprot)
+                        self.reservations.append(_elem18)
+                    iprot.readListEnd()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -365,14 +505,21 @@ class GroupComputeResourcePreference(object):
         if self.groupSSHAccountProvisionerConfigs is not None:
             oprot.writeFieldBegin('groupSSHAccountProvisionerConfigs', TType.LIST, 17)
             oprot.writeListBegin(TType.STRUCT, len(self.groupSSHAccountProvisionerConfigs))
-            for iter6 in self.groupSSHAccountProvisionerConfigs:
-                iter6.write(oprot)
+            for iter19 in self.groupSSHAccountProvisionerConfigs:
+                iter19.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.sshAccountProvisionerAdditionalInfo is not None:
             oprot.writeFieldBegin('sshAccountProvisionerAdditionalInfo', TType.STRING, 18)
             oprot.writeString(self.sshAccountProvisionerAdditionalInfo.encode('utf-8') if sys.version_info[0] == 2 else self.sshAccountProvisionerAdditionalInfo)
             oprot.writeFieldEnd()
+        if self.reservations is not None:
+            oprot.writeFieldBegin('reservations', TType.LIST, 19)
+            oprot.writeListBegin(TType.STRUCT, len(self.reservations))
+            for iter20 in self.reservations:
+                iter20.write(oprot)
+            oprot.writeListEnd()
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
@@ -447,10 +594,10 @@ class ComputeResourcePolicy(object):
             elif fid == 4:
                 if ftype == TType.LIST:
                     self.allowedBatchQueues = []
-                    (_etype10, _size7) = iprot.readListBegin()
-                    for _i11 in range(_size7):
-                        _elem12 = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
-                        self.allowedBatchQueues.append(_elem12)
+                    (_etype24, _size21) = iprot.readListBegin()
+                    for _i25 in range(_size21):
+                        _elem26 = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                        self.allowedBatchQueues.append(_elem26)
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
@@ -479,8 +626,8 @@ class ComputeResourcePolicy(object):
         if self.allowedBatchQueues is not None:
             oprot.writeFieldBegin('allowedBatchQueues', TType.LIST, 4)
             oprot.writeListBegin(TType.STRING, len(self.allowedBatchQueues))
-            for iter13 in self.allowedBatchQueues:
-                oprot.writeString(iter13.encode('utf-8') if sys.version_info[0] == 2 else iter13)
+            for iter27 in self.allowedBatchQueues:
+                oprot.writeString(iter27.encode('utf-8') if sys.version_info[0] == 2 else iter27)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         oprot.writeFieldStop()
@@ -730,33 +877,33 @@ class GroupResourceProfile(object):
             elif fid == 4:
                 if ftype == TType.LIST:
                     self.computePreferences = []
-                    (_etype17, _size14) = iprot.readListBegin()
-                    for _i18 in range(_size14):
-                        _elem19 = GroupComputeResourcePreference()
-                        _elem19.read(iprot)
-                        self.computePreferences.append(_elem19)
+                    (_etype31, _size28) = iprot.readListBegin()
+                    for _i32 in range(_size28):
+                        _elem33 = GroupComputeResourcePreference()
+                        _elem33.read(iprot)
+                        self.computePreferences.append(_elem33)
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
             elif fid == 5:
                 if ftype == TType.LIST:
                     self.computeResourcePolicies = []
-                    (_etype23, _size20) = iprot.readListBegin()
-                    for _i24 in range(_size20):
-                        _elem25 = ComputeResourcePolicy()
-                        _elem25.read(iprot)
-                        self.computeResourcePolicies.append(_elem25)
+                    (_etype37, _size34) = iprot.readListBegin()
+                    for _i38 in range(_size34):
+                        _elem39 = ComputeResourcePolicy()
+                        _elem39.read(iprot)
+                        self.computeResourcePolicies.append(_elem39)
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
             elif fid == 6:
                 if ftype == TType.LIST:
                     self.batchQueueResourcePolicies = []
-                    (_etype29, _size26) = iprot.readListBegin()
-                    for _i30 in range(_size26):
-                        _elem31 = BatchQueueResourcePolicy()
-                        _elem31.read(iprot)
-                        self.batchQueueResourcePolicies.append(_elem31)
+                    (_etype43, _size40) = iprot.readListBegin()
+                    for _i44 in range(_size40):
+                        _elem45 = BatchQueueResourcePolicy()
+                        _elem45.read(iprot)
+                        self.batchQueueResourcePolicies.append(_elem45)
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
@@ -800,22 +947,22 @@ class GroupResourceProfile(object):
         if self.computePreferences is not None:
             oprot.writeFieldBegin('computePreferences', TType.LIST, 4)
             oprot.writeListBegin(TType.STRUCT, len(self.computePreferences))
-            for iter32 in self.computePreferences:
-                iter32.write(oprot)
+            for iter46 in self.computePreferences:
+                iter46.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.computeResourcePolicies is not None:
             oprot.writeFieldBegin('computeResourcePolicies', TType.LIST, 5)
             oprot.writeListBegin(TType.STRUCT, len(self.computeResourcePolicies))
-            for iter33 in self.computeResourcePolicies:
-                iter33.write(oprot)
+            for iter47 in self.computeResourcePolicies:
+                iter47.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.batchQueueResourcePolicies is not None:
             oprot.writeFieldBegin('batchQueueResourcePolicies', TType.LIST, 6)
             oprot.writeListBegin(TType.STRUCT, len(self.batchQueueResourcePolicies))
-            for iter34 in self.batchQueueResourcePolicies:
-                iter34.write(oprot)
+            for iter48 in self.batchQueueResourcePolicies:
+                iter48.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.creationTime is not None:
diff --git a/airavata/model/sharing/ttypes.py b/airavata/model/sharing/ttypes.py
index 1f2f0e9..661367f 100644
--- a/airavata/model/sharing/ttypes.py
+++ b/airavata/model/sharing/ttypes.py
@@ -190,6 +190,7 @@ class Domain(object):
     <li>description : A short description for the domain</li>
     <li>createdTime : Will be set by the system</li>
     <li>updatedTime : Will be set by the system</li>
+    <li>initialUserGroupId : New users will automatically be added to this group</li>
 
 
     Attributes:
@@ -198,6 +199,7 @@ class Domain(object):
      - description
      - createdTime
      - updatedTime
+     - initialUserGroupId
     """
 
     thrift_spec = (
@@ -207,14 +209,16 @@ class Domain(object):
         (3, TType.STRING, 'description', 'UTF8', None, ),  # 3
         (4, TType.I64, 'createdTime', None, None, ),  # 4
         (5, TType.I64, 'updatedTime', None, None, ),  # 5
+        (6, TType.STRING, 'initialUserGroupId', 'UTF8', None, ),  # 6
     )
 
-    def __init__(self, domainId=thrift_spec[1][4], name=None, description=None, createdTime=None, updatedTime=None,):
+    def __init__(self, domainId=thrift_spec[1][4], name=None, description=None, createdTime=None, updatedTime=None, initialUserGroupId=None,):
         self.domainId = domainId
         self.name = name
         self.description = description
         self.createdTime = createdTime
         self.updatedTime = updatedTime
+        self.initialUserGroupId = initialUserGroupId
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -250,6 +254,11 @@ class Domain(object):
                     self.updatedTime = iprot.readI64()
                 else:
                     iprot.skip(ftype)
+            elif fid == 6:
+                if ftype == TType.STRING:
+                    self.initialUserGroupId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -280,6 +289,10 @@ class Domain(object):
             oprot.writeFieldBegin('updatedTime', TType.I64, 5)
             oprot.writeI64(self.updatedTime)
             oprot.writeFieldEnd()
+        if self.initialUserGroupId is not None:
+            oprot.writeFieldBegin('initialUserGroupId', TType.STRING, 6)
+            oprot.writeString(self.initialUserGroupId.encode('utf-8') if sys.version_info[0] == 2 else self.initialUserGroupId)
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
diff --git a/airavata/service/profile/user/cpi/UserProfileService.py b/airavata/service/profile/user/cpi/UserProfileService.py
index ca8b8b0..98f33fb 100644
--- a/airavata/service/profile/user/cpi/UserProfileService.py
+++ b/airavata/service/profile/user/cpi/UserProfileService.py
@@ -31,6 +31,10 @@ class Iface(airavata.base.api.BaseAPI.Iface):
 
     def addUserProfile(self, authzToken, userProfile):
         """
+        This method is deprecated and will be removed in future versions of Airavata. Please use
+        the initializeUserProfile method which fetches the information about the user profile from
+        IAM service - @Deprecated
+
         Parameters:
          - authzToken
          - userProfile
@@ -154,6 +158,10 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
 
     def addUserProfile(self, authzToken, userProfile):
         """
+        This method is deprecated and will be removed in future versions of Airavata. Please use
+        the initializeUserProfile method which fetches the information about the user profile from
+        IAM service - @Deprecated
+
         Parameters:
          - authzToken
          - userProfile
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index 794a552..0ab37a6 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -7,10 +7,9 @@
             v-if="localGroupResourceProfile"
             class="group-resource-profile-name text-muted text-uppercase"
           >
-            <i
-              class="fa fa-server"
-              aria-hidden="true"
-            ></i> {{ localGroupResourceProfile.groupResourceProfileName }}</div>
+            <i class="fa fa-server" aria-hidden="true"></i>
+            {{ localGroupResourceProfile.groupResourceProfileName }}
+          </div>
           {{ computeResource.hostName }}
         </h1>
       </div>
@@ -22,7 +21,9 @@
             <b-form-group
               label="Login Username"
               label-for="login-username"
-              :invalid-feedback="validationFeedback.loginUserName.invalidFeedback"
+              :invalid-feedback="
+                validationFeedback.loginUserName.invalidFeedback
+              "
               :state="validationFeedback.loginUserName.state"
             >
               <b-form-input
@@ -42,16 +43,22 @@
               <ssh-credential-selector
                 v-model="data.resourceSpecificCredentialStoreToken"
                 v-if="localGroupResourceProfile"
-                :null-option-default-credential-token="localGroupResourceProfile.defaultCredentialStoreToken"
-                :null-option-disabled="!localGroupResourceProfile.defaultCredentialStoreToken"
+                :null-option-default-credential-token="
+                  localGroupResourceProfile.defaultCredentialStoreToken
+                "
+                :null-option-disabled="
+                  !localGroupResourceProfile.defaultCredentialStoreToken
+                "
               >
                 <template
                   slot="null-option-label"
                   slot-scope="nullOptionLabelScope"
                 >
                   <span v-if="nullOptionLabelScope.defaultCredentialSummary">
-                    Use the default SSH credential for {{ localGroupResourceProfile.groupResourceProfileName }} ({{
-                    nullOptionLabelScope.defaultCredentialSummary.description }})
+                    Use the default SSH credential for
+                    {{ localGroupResourceProfile.groupResourceProfileName }} ({{
+                      nullOptionLabelScope.defaultCredentialSummary.description
+                    }})
                   </span>
                   <span v-else>
                     Select a SSH credential
@@ -73,7 +80,9 @@
             <b-form-group
               label="Scratch Location"
               label-for="scratch-location"
-              :invalid-feedback="validationFeedback.scratchLocation.invalidFeedback"
+              :invalid-feedback="
+                validationFeedback.scratchLocation.invalidFeedback
+              "
               :state="validationFeedback.scratchLocation.state"
             >
               <b-form-input
@@ -104,15 +113,27 @@
                 :key="batchQueue.queueName"
               >
                 <b-form-checkbox
-                  :checked="localComputeResourcePolicy.allowedBatchQueues.includes(batchQueue.queueName)"
+                  :checked="
+                    localComputeResourcePolicy.allowedBatchQueues.includes(
+                      batchQueue.queueName
+                    )
+                  "
                   @input="batchQueueChecked(batchQueue, $event)"
                 >
                   {{ batchQueue.queueName }}
                 </b-form-checkbox>
                 <batch-queue-resource-policy
-                  v-if="localComputeResourcePolicy.allowedBatchQueues.includes(batchQueue.queueName)"
+                  v-if="
+                    localComputeResourcePolicy.allowedBatchQueues.includes(
+                      batchQueue.queueName
+                    )
+                  "
                   :batch-queue="batchQueue"
-                  :value="localBatchQueueResourcePolicies.find(pol => pol.queuename === batchQueue.queueName)"
+                  :value="
+                    localBatchQueueResourcePolicies.find(
+                      pol => pol.queuename === batchQueue.queueName
+                    )
+                  "
                   @input="updatedBatchQueueResourcePolicy(batchQueue, $event)"
                   @valid="recordValidBatchQueueResourcePolicy(batchQueue)"
                   @invalid="recordInvalidBatchQueueResourcePolicy(batchQueue)"
@@ -124,22 +145,31 @@
       </div>
     </div>
     <div class="row">
+      <div class="col">
+        <div class="card">
+          <div class="card-body">
+            <compute-resource-reservation-list
+              :reservations="data.reservations"
+              :queues="queueNames"
+              @added="addReservation"
+              @deleted="deleteReservation"
+              @updated="updateReservation"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row">
       <div class="col d-flex justify-content-end">
-        <b-button
-          variant="primary"
-          @click="save"
-          :disabled="!valid"
-        >Save</b-button>
-        <b-button
-          class="ml-2"
-          variant="danger"
-          @click="remove"
-        >Delete</b-button>
-        <b-button
-          class="ml-2"
-          variant="secondary"
-          @click="cancel"
-        >Cancel</b-button>
+        <b-button variant="primary" @click="save" :disabled="!valid"
+          >Save</b-button
+        >
+        <b-button class="ml-2" variant="danger" @click="remove"
+          >Delete</b-button
+        >
+        <b-button class="ml-2" variant="secondary" @click="cancel"
+          >Cancel</b-button
+        >
       </div>
     </div>
   </div>
@@ -149,6 +179,7 @@
 import DjangoAiravataAPI from "django-airavata-api";
 import BatchQueueResourcePolicy from "./BatchQueueResourcePolicy.vue";
 import SSHCredentialSelector from "../../credentials/SSHCredentialSelector.vue";
+import ComputeResourceReservationList from "./ComputeResourceReservationList";
 
 import { models, services, errors } from "django-airavata-api";
 import {
@@ -161,7 +192,8 @@ export default {
   name: "compute-preference",
   components: {
     BatchQueueResourcePolicy,
-    "ssh-credential-selector": SSHCredentialSelector
+    "ssh-credential-selector": SSHCredentialSelector,
+    ComputeResourceReservationList
   },
   props: {
     id: {
@@ -255,6 +287,9 @@ export default {
       return this.invalidBatchQueueResourcePolicies.filter(queueName =>
         this.localComputeResourcePolicy.allowedBatchQueues.includes(queueName)
       );
+    },
+    queueNames() {
+      return this.computeResource.batchQueues.map(bq => bq.queueName);
     }
   },
   mixins: [mixins.VModelMixin],
@@ -436,6 +471,15 @@ export default {
       } else {
         this.$emit("invalid");
       }
+    },
+    addReservation(reservation) {
+      this.data.reservations.push(reservation);
+    },
+    deleteReservation(reservationIndex) {
+      this.data.reservations.splice(reservationIndex, 1);
+    },
+    updateReservation(reservation, reservationIndex) {
+      this.data.reservations.splice(reservationIndex, 1, reservation);
     }
   },
   beforeRouteEnter: function(to, from, next) {
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
new file mode 100644
index 0000000..0d18ba7
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
@@ -0,0 +1,139 @@
+<template>
+  <b-form @input="valuesChanged">
+    <b-form-group
+      label="Reservation name"
+      label-for="reservation-name"
+      :invalid-feedback="nameValidationFeedback"
+      :state="nameValidationState"
+    >
+      <b-form-input
+        id="reservation-name"
+        v-model="data.reservationName"
+        type="text"
+        @input="nameInputBegins = true"
+        :state="nameValidationState"
+      />
+    </b-form-group>
+    <b-form-group label="Start Time" label-for="start-time">
+      <datetime
+        id="start-time"
+        type="datetime"
+        :value="startTimeAsString"
+        input-class="form-control"
+        :format="{
+          year: 'numeric',
+          month: '2-digit',
+          day: 'numeric',
+          hour: 'numeric',
+          minute: '2-digit',
+          timeZoneName: 'short'
+        }"
+        :phrases="{ ok: 'Continue', cancel: 'Exit' }"
+        :hour-step="1"
+        :minute-step="30"
+        :week-start="7"
+        use12-hour
+        auto
+        @input="data.startTime = stringToDate($event)"
+      ></datetime>
+    </b-form-group>
+    <b-form-group label="End Time" label-for="end-time">
+      <datetime
+        id="end-time"
+        type="datetime"
+        :value="endTimeAsString"
+        input-class="form-control"
+        :format="{
+          year: 'numeric',
+          month: '2-digit',
+          day: 'numeric',
+          hour: 'numeric',
+          minute: '2-digit',
+          timeZoneName: 'short'
+        }"
+        :phrases="{ ok: 'Continue', cancel: 'Exit' }"
+        :hour-step="1"
+        :minute-step="30"
+        :week-start="7"
+        use12-hour
+        auto
+        @input="data.endTime = stringToDate($event)"
+      ></datetime>
+    </b-form-group>
+    <b-form-group label="Queues" label-for="queues"
+      :invalid-feedback="getValidationFeedback('queueNames')"
+      :state="getValidationState('queueNames')"
+    >
+      <b-form-checkbox-group
+      id="queues"
+      v-model="data.queueNames"
+      :options="queueNameOptions"
+      :state="getValidationState('queueNames')"
+      />
+    </b-form-group>
+  </b-form>
+</template>
+
+<script>
+import { mixins, utils } from "django-airavata-common-ui";
+import { Datetime } from "vue-datetime";
+import "vue-datetime/dist/vue-datetime.css";
+
+export default {
+  name: "compute-resource-reservation-editor",
+  mixins: [mixins.VModelMixin],
+  components: {
+    datetime: Datetime
+  },
+  props: {
+    queues: {
+      type: Array,
+      required: true
+    }
+  },
+  data() {
+    return {
+      nameInputBegins: false
+    };
+  },
+  computed: {
+    startTimeAsString() {
+      return this.data.startTime.toISOString();
+    },
+    endTimeAsString() {
+      return this.data.endTime.toISOString();
+    },
+    nameValidationFeedback() {
+      return this.getValidationFeedback('reservationName');
+    },
+    nameValidationState() {
+      if (this.nameInputBegins === false) {
+        return null;
+      }
+      return this.getValidationState('reservationName');
+    },
+    queueNameOptions() {
+      return this.queues.slice().sort();
+    }
+  },
+  methods: {
+    stringToDate(datetimeString) {
+      return new Date(datetimeString);
+    },
+    getValidationFeedback: function(properties) {
+      return utils.getProperty(this.data.validate(), properties);
+    },
+    getValidationState: function(properties) {
+      return this.getValidationFeedback(properties) ? "invalid" : null;
+    },
+    valuesChanged() {
+      const validationResults = this.data.validate();
+      if (Object.keys(validationResults).length === 0) {
+        this.$emit('valid');
+      } else {
+        this.$emit('invalid');
+      }
+    }
+  }
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
new file mode 100644
index 0000000..2b2671b
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
@@ -0,0 +1,189 @@
+<template>
+  <list-layout
+    @add-new-item="addNewReservation"
+    :items="decoratedReservations"
+    title="Reservations"
+    new-item-button-text="New Reservation"
+    :new-button-disabled="readonly"
+  >
+    <template slot="new-item-editor">
+      <b-card v-if="showNewItemEditor" title="New Reservation">
+        <compute-resource-reservation-editor
+          v-model="newReservation"
+          :queues="queues"
+          @valid="newReservationValid = true"
+          @invalid="newReservationValid = false"
+        />
+        <div class="row">
+          <div class="col">
+            <b-button
+              variant="primary"
+              @click="saveNewReservation"
+              :disabled="isSaveDisabled"
+            >
+              Add
+            </b-button>
+            <b-button variant="secondary" @click="cancelNewReservation">
+              Cancel
+            </b-button>
+          </div>
+        </div>
+      </b-card>
+    </template>
+    <template slot="item-list" slot-scope="slotProps">
+      <b-table
+        striped
+        hover
+        :fields="fields"
+        :items="slotProps.items"
+        sort-by="startTime"
+      >
+        <template slot="queueNames" slot-scope="data">
+          <ul v-for="queueName in data.item.queueNames" :key="queueName">
+            <li>{{ queueName }}</li>
+          </ul>
+        </template>
+        <template slot="action" slot-scope="data">
+          <b-link
+            v-if="!readonly"
+            class="action-link"
+            @click="toggleDetails(data)"
+          >
+            Edit
+            <i class="fa fa-edit" aria-hidden="true"></i>
+          </b-link>
+          <delete-link
+            v-if="!readonly"
+            class="action-link"
+            @delete="deleteReservation(data.item)"
+          >
+            Are you sure you want to delete reservation
+            {{ data.item.reservationName }}?
+          </delete-link>
+        </template>
+        <template slot="row-details" slot-scope="row">
+          <b-card>
+            <compute-resource-reservation-editor
+              :value="row.item"
+              @input="updatedReservation"
+              :queues="queues"
+            />
+            <b-button size="sm" @click="toggleDetails(row)">Close</b-button>
+          </b-card>
+        </template>
+      </b-table>
+    </template>
+  </list-layout>
+</template>
+
+<script>
+import { models } from "django-airavata-api";
+import { components, layouts, utils } from "django-airavata-common-ui";
+import ComputeResourceReservationEditor from "./ComputeResourceReservationEditor";
+
+export default {
+  name: "compute-resource-reservation-list",
+  components: {
+    "delete-link": components.DeleteLink,
+    "human-date": components.HumanDate,
+    "list-layout": layouts.ListLayout,
+    ComputeResourceReservationEditor
+  },
+  props: {
+    reservations: {
+      type: Array,
+      required: true
+    },
+    queues: {
+      type: Array,
+      required: true
+    },
+    readonly: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      showingDetails: {},
+      showNewItemEditor: false,
+      newReservation: null,
+      newReservationValid: false
+    };
+  },
+  computed: {
+    fields() {
+      return [
+        {
+          label: "Name",
+          key: "reservationName"
+        },
+        {
+          label: "Queues",
+          key: "queueNames"
+        },
+        {
+          label: "Start Time",
+          key: "startTime",
+          sortable: true,
+          formatter: value =>
+            utils.dateFormatters.dateTimeInMinutesWithTimeZone.format(value)
+        },
+        {
+          label: "End Time",
+          key: "endTime",
+          sortable: true,
+          formatter: value =>
+            utils.dateFormatters.dateTimeInMinutesWithTimeZone.format(value)
+        },
+        {
+          label: "Action",
+          key: "action"
+        }
+      ];
+    },
+    decoratedReservations() {
+      return this.reservations
+        ? this.reservations.map(res => {
+            const resClone = res.clone();
+            resClone._showDetails = this.showingDetails[resClone.key];
+            return resClone;
+          })
+        : [];
+    },
+    isSaveDisabled() {
+      return !this.newReservationValid;
+    }
+  },
+  created() {},
+  methods: {
+    updatedReservation(newValue) {
+      const reservationIndex = this.reservations.findIndex(r => r.key === newValue.key);
+      this.$emit("updated", newValue, reservationIndex);
+    },
+    toggleDetails(row) {
+      row.toggleDetails();
+      this.showingDetails[row.item.key] = !this.showingDetails[
+        row.item.key
+      ];
+    },
+    deleteReservation(reservation) {
+      const reservationIndex = this.reservations.findIndex(r => r.key === reservation.key);
+      this.$emit("deleted", reservationIndex);
+    },
+    addNewReservation() {
+      this.newReservation = new models.ComputeResourceReservation();
+      this.newReservationValid = false;
+      this.newReservation.queueNames = this.queues.slice();
+      this.showNewItemEditor = true;
+    },
+    saveNewReservation() {
+      this.$emit("added", this.newReservation);
+      this.showNewItemEditor = false;
+    },
+    cancelNewReservation() {
+      this.showNewItemEditor = false;
+    }
+  }
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/gatewayprofile/StoragePreferenceList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/gatewayprofile/StoragePreferenceList.vue
index 145d0e0..af2122c 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/gatewayprofile/StoragePreferenceList.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/gatewayprofile/StoragePreferenceList.vue
@@ -33,7 +33,7 @@
             Edit
             <i class="fa fa-edit" aria-hidden="true"></i>
           </b-link>
-          <delete-link v-if="!readonly" @delete="deleteStoragePreference(data.item.storageResourceId)">
+          <delete-link v-if="!readonly" class="action-link" @delete="deleteStoragePreference(data.item.storageResourceId)">
             Are you sure you want to delete the storage preference for {{ getStorageResourceName(data.item.storageResourceId) }}?
           </delete-link>
         </template>
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 58b1470..2cf160f 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -27,6 +27,7 @@ from airavata.model.appcatalog.gatewayprofile.ttypes import (
     StoragePreference
 )
 from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+    ComputeResourceReservation,
     GroupComputeResourcePreference,
     GroupResourceProfile
 )
@@ -567,9 +568,15 @@ class UserProfileSerializer(
     lastAccessTime = UTCPosixTimestampDateTimeField()
 
 
+class ComputeResourceReservationSerializer(
+        thrift_utils.create_serializer_class(ComputeResourceReservation)):
+    startTime = UTCPosixTimestampDateTimeField(allow_null=True)
+    endTime = UTCPosixTimestampDateTimeField(allow_null=True)
+
+
 class GroupComputeResourcePreferenceSerializer(
         thrift_utils.create_serializer_class(GroupComputeResourcePreference)):
-    pass
+    reservations = ComputeResourceReservationSerializer(many=True)
 
 
 class GroupResourceProfileSerializer(
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 5107811..a6be8d5 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -11,6 +11,7 @@ import BatchQueue from "./models/BatchQueue";
 import BatchQueueResourcePolicy from "./models/BatchQueueResourcePolicy";
 import CommandObject from "./models/CommandObject";
 import ComputeResourcePolicy from "./models/ComputeResourcePolicy";
+import ComputeResourceReservation from "./models/ComputeResourceReservation";
 import DataProduct from "./models/DataProduct";
 import DataType from "./models/DataType";
 import Experiment from "./models/Experiment";
@@ -68,6 +69,7 @@ const models = {
   BatchQueueResourcePolicy,
   CommandObject,
   ComputeResourcePolicy,
+  ComputeResourceReservation,
   DataProduct,
   DataType,
   Experiment,
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
new file mode 100644
index 0000000..9a437b4
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
@@ -0,0 +1,41 @@
+import BaseModel from "./BaseModel";
+import uuidv4 from "uuid/v4";
+
+const FIELDS = [
+  "reservationId",
+  "reservationName",
+  {
+    name: "queueNames",
+    type: "string",
+    list: true
+  },
+  {
+    name: "startTime",
+    type: Date,
+    default: () => new Date(),
+
+  },
+  {
+    name: "endTime",
+    type: Date,
+    default: () => new Date(),
+  }
+];
+
+export default class ComputeResourceReservation extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+    this._key = data.key ? data.key : uuidv4();
+  }
+  get key() {
+    return this._key;
+  }
+  validate() {
+    let validationResults = {};
+    if (this.isEmpty(this.reservationName)) {
+      validationResults["reservationName"] =
+        "Please provide the name of this reservation.";
+    }
+    return validationResults;
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js b/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
index facaa85..7e8b46e 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
@@ -1,4 +1,5 @@
 import BaseModel from "./BaseModel";
+import ComputeResourceReservation from "./ComputeResourceReservation";
 
 const FIELDS = [
   "computeResourceId",
@@ -22,7 +23,12 @@ const FIELDS = [
   "reservationEndTime",
   "sshAccountProvisioner",
   "groupSSHAccountProvisionerConfigs",
-  "sshAccountProvisionerAdditionalInfo"
+  "sshAccountProvisionerAdditionalInfo",
+  {
+    name: "reservations",
+    type: ComputeResourceReservation,
+    list: true
+  }
 ];
 
 export default class GroupComputeResourcePreference extends BaseModel {
diff --git a/django_airavata/settings_local.py.ide b/django_airavata/settings_local.py.ide
index 5918819..548e82d 100644
--- a/django_airavata/settings_local.py.ide
+++ b/django_airavata/settings_local.py.ide
@@ -26,7 +26,11 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 # EMAIL_HOST_USER = '...'
 # EMAIL_HOST_PASSWORD = '...'
 # EMAIL_USE_TLS = True
-# ADMINS = [('Admin Name', 'admin@example.com')]
+ADMINS = [('Admin Name', 'admin@example.com')]
+# PORTAL_ADMINS receive administrative emails, like when a new user is created
+# This can be set to a different value than ADMINS so that the PORTAL_ADMINS
+# don't receive error emails
+PORTAL_ADMINS = ADMINS
 # SERVER_EMAIL = 'portal@example.com'
 
 # Keycloak Configuration
diff --git a/django_airavata/static/common/js/components/HumanDate.vue b/django_airavata/static/common/js/components/HumanDate.vue
index 26bd3fa..03f870c 100644
--- a/django_airavata/static/common/js/components/HumanDate.vue
+++ b/django_airavata/static/common/js/components/HumanDate.vue
@@ -1,5 +1,5 @@
 <template>
-  <span :title="date.toString()">{{ fromNow }}</span>
+  <abbr :title="date.toString()">{{ fromNow }}</abbr>
 </template>
 <script>
 import moment from "moment";
diff --git a/django_airavata/static/common/js/utils.js b/django_airavata/static/common/js/utils.js
index 21ff86c..54d43f1 100644
--- a/django_airavata/static/common/js/utils.js
+++ b/django_airavata/static/common/js/utils.js
@@ -1,13 +1,26 @@
-
 export function getProperty(obj, props) {
-    if (typeof props === 'string') {
-        return obj[props];
-    } else if (typeof props === 'object' && props instanceof Array) { // Array
-        return props.reduce((o, prop) => o && prop in o ? o[prop] : undefined, obj);
-    }
+  if (typeof props === "string") {
+    return obj[props];
+  } else if (typeof props === "object" && props instanceof Array) {
+    // Array
+    return props.reduce(
+      (o, prop) => (o && prop in o ? o[prop] : undefined),
+      obj
+    );
+  }
 }
 export function sanitizeHTMLId(id) {
   // Replace anything that isn't an HTML safe id character with underscore
   // Here safe means allowable by HTML5 and also safe to use in a jQuery selector
   return id.replace(/[^a-zA-Z0-9_-]/g, "_");
 }
+export const dateFormatters = {
+  dateTimeInMinutesWithTimeZone: new Intl.DateTimeFormat(undefined, {
+    year: "numeric",
+    month: "2-digit",
+    day: "2-digit",
+    hour: "numeric",
+    minute: "numeric",
+    timeZoneName: "short"
+  })
+};


[airavata-django-portal] 03/09: AIRAVATA-3126 Add support for deleting all expired reservations

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 1a4b75609ebcffc0c1caf5fd3e3151e83f5b5a5d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Mar 20 17:26:37 2020 -0400

    AIRAVATA-3126 Add support for deleting all expired reservations
---
 .../ComputePreference.vue                          | 10 ++++--
 .../ComputeResourceReservationList.vue             | 38 +++++++++++++++-------
 .../js/models/ComputeResourceReservation.js        |  4 +++
 .../static/common/js/components/DeleteButton.vue   |  6 +++-
 .../static/common/js/layouts/ListLayout.vue        |  2 ++
 5 files changed, 46 insertions(+), 14 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index 74294d8..5ad366e 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -479,10 +479,16 @@ export default {
     addReservation(reservation) {
       this.data.reservations.push(reservation);
     },
-    deleteReservation(reservationIndex) {
+    deleteReservation(reservation) {
+      const reservationIndex = this.data.reservations.findIndex(
+        r => r.key === reservation.key
+      );
       this.data.reservations.splice(reservationIndex, 1);
     },
-    updateReservation(reservation, reservationIndex) {
+    updateReservation(reservation) {
+      const reservationIndex = this.data.reservations.findIndex(
+        r => r.key === reservation.key
+      );
       this.data.reservations.splice(reservationIndex, 1, reservation);
     }
   },
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
index 2415291..8c23922 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
@@ -6,6 +6,16 @@
     new-item-button-text="New Reservation"
     :new-button-disabled="readonly"
   >
+    <template slot="additional-buttons">
+      <delete-button
+        class="mr-2"
+        @delete="deleteAllExpiredReservations"
+        label="Delete All Expired"
+        :disabled="expiredReservations.length === 0"
+      >
+        Are you sure you want to delete all expired reservations?
+      </delete-button>
+    </template>
     <template slot="new-item-editor">
       <b-card v-if="showNewItemEditor" title="New Reservation">
         <compute-resource-reservation-editor
@@ -38,12 +48,15 @@
     </template>
     <template slot="item-list" slot-scope="slotProps">
       <b-table
-        striped
         hover
         :fields="fields"
         :items="slotProps.items"
         sort-by="startTime"
       >
+        <template slot="reservationName" slot-scope="data">
+          {{ data.value }}
+          <b-badge v-if="data.item.isExpired">Expired</b-badge>
+        </template>
         <template slot="queueNames" slot-scope="data">
           <ul v-for="queueName in data.item.queueNames" :key="queueName">
             <li>{{ queueName }}</li>
@@ -54,7 +67,7 @@
             v-if="!readonly"
             class="action-link"
             @click="toggleDetails(data)"
-              :disabled="isReservationInvalid(data.item.key)"
+            :disabled="isReservationInvalid(data.item.key)"
           >
             Edit
             <i class="fa fa-edit" aria-hidden="true"></i>
@@ -101,7 +114,8 @@ export default {
     "delete-link": components.DeleteLink,
     "human-date": components.HumanDate,
     "list-layout": layouts.ListLayout,
-    ComputeResourceReservationEditor
+    ComputeResourceReservationEditor,
+    "delete-button": components.DeleteButton
   },
   props: {
     reservations: {
@@ -174,26 +188,25 @@ export default {
         (!this.showNewItemEditor || this.newReservationValid) &&
         this.invalidReservations.length === 0
       );
+    },
+    expiredReservations() {
+      return this.reservations
+        ? this.reservations.filter(r => r.isExpired)
+        : [];
     }
   },
   created() {},
   methods: {
     updatedReservation(newValue) {
-      const reservationIndex = this.reservations.findIndex(
-        r => r.key === newValue.key
-      );
-      this.$emit("updated", newValue, reservationIndex);
+      this.$emit("updated", newValue);
     },
     toggleDetails(row) {
       row.toggleDetails();
       this.showingDetails[row.item.key] = !this.showingDetails[row.item.key];
     },
     deleteReservation(reservation) {
-      const reservationIndex = this.reservations.findIndex(
-        r => r.key === reservation.key
-      );
       this.removeInvalidReservation(reservation.key);
-      this.$emit("deleted", reservationIndex);
+      this.$emit("deleted", reservation);
     },
     addNewReservation() {
       this.newReservation = new models.ComputeResourceReservation();
@@ -230,6 +243,9 @@ export default {
       } else {
         this.$emit("invalid");
       }
+    },
+    deleteAllExpiredReservations() {
+      this.expiredReservations.forEach(this.deleteReservation);
     }
   }
 };
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
index c4a8921..2303ad7 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
@@ -43,4 +43,8 @@ export default class ComputeResourceReservation extends BaseModel {
     }
     return validationResults;
   }
+  get isExpired() {
+    const now = new Date();
+    return now > this.endTime;
+  }
 }
diff --git a/django_airavata/static/common/js/components/DeleteButton.vue b/django_airavata/static/common/js/components/DeleteButton.vue
index f1a22aa..0e07c43 100644
--- a/django_airavata/static/common/js/components/DeleteButton.vue
+++ b/django_airavata/static/common/js/components/DeleteButton.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="delete-button">
     <b-button variant="danger" @click="$refs.modal.show()" :disabled="disabled">
-      Delete
+      {{ label }}
     </b-button>
     <confirmation-dialog ref="modal" :title="dialogTitle" @ok="$emit('delete')">
       <slot></slot>
@@ -21,6 +21,10 @@ export default {
     disabled: {
       type: Boolean,
       default: false
+    },
+    label: {
+      type: String,
+      default: "Delete"
     }
   },
   components: {
diff --git a/django_airavata/static/common/js/layouts/ListLayout.vue b/django_airavata/static/common/js/layouts/ListLayout.vue
index d94628f..dfd77fa 100644
--- a/django_airavata/static/common/js/layouts/ListLayout.vue
+++ b/django_airavata/static/common/js/layouts/ListLayout.vue
@@ -7,6 +7,8 @@
         </slot>
       </div>
       <div class="col-auto">
+        <slot name="additional-buttons">
+        </slot>
         <slot name="new-item-button">
           <b-btn variant="primary" @click="addNewItem" :disabled="newButtonDisabled">
             {{ newItemButtonText }}


[airavata-django-portal] 06/09: AIRAVATA-3216 Don't allow picking an end time before start time

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit f7aed1cff1fd997a4d1f272eea32dc13537f9669
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Mar 21 11:47:13 2020 -0400

    AIRAVATA-3216 Don't allow picking an end time before start time
---
 .../group_resource_preferences/ComputeResourceReservationEditor.vue      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
index b10d269..04b9a93 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
@@ -68,6 +68,7 @@
         :hour-step="1"
         :minute-step="30"
         :week-start="7"
+        :min-datetime="startTimeAsString"
         use12-hour
         auto
         @input="data.endTime = stringToDate($event)"


[airavata-django-portal] 02/09: AIRAVATA-3216 ComputeResourceReservation validations

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit ac818b43e18ece9491f39cae5d57d0473067fde1
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Mar 10 11:52:34 2020 -0400

    AIRAVATA-3216 ComputeResourceReservation validations
---
 .../ComputePreference.vue                          |   8 +-
 .../ComputeResourceReservationEditor.vue           |  44 ++--
 .../ComputeResourceReservationList.vue             |  65 +++++-
 .../django_airavata_api/js/models/BaseModel.js     | 229 ++++++++++++---------
 .../js/models/ComputeResourceReservation.js        |  11 +-
 5 files changed, 229 insertions(+), 128 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index 0ab37a6..74294d8 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -154,6 +154,8 @@
               @added="addReservation"
               @deleted="deleteReservation"
               @updated="updateReservation"
+              @valid="reservationsInvalid = false"
+              @invalid="reservationsInvalid = true"
             />
           </div>
         </div>
@@ -264,7 +266,8 @@ export default {
         jobSubmissionInterfaces: []
       },
       validationErrors: null,
-      invalidBatchQueueResourcePolicies: []
+      invalidBatchQueueResourcePolicies: [],
+      reservationsInvalid: false
     };
   },
   computed: {
@@ -280,7 +283,8 @@ export default {
     valid() {
       return (
         this.allowedInvalidBatchQueueResourcePolicies.length === 0 &&
-        Object.keys(this.groupComputeResourceValidation).length === 0
+        Object.keys(this.groupComputeResourceValidation).length === 0 &&
+        !this.reservationsInvalid
       );
     },
     allowedInvalidBatchQueueResourcePolicies() {
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
index 0d18ba7..b10d269 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationEditor.vue
@@ -1,5 +1,5 @@
 <template>
-  <b-form @input="valuesChanged">
+  <b-form>
     <b-form-group
       label="Reservation name"
       label-for="reservation-name"
@@ -14,7 +14,12 @@
         :state="nameValidationState"
       />
     </b-form-group>
-    <b-form-group label="Start Time" label-for="start-time">
+    <b-form-group
+      label="Start Time"
+      label-for="start-time"
+      :invalid-feedback="getValidationFeedback('startTime')"
+      :state="getValidationState('startTime')"
+    >
       <datetime
         id="start-time"
         type="datetime"
@@ -37,12 +42,20 @@
         @input="data.startTime = stringToDate($event)"
       ></datetime>
     </b-form-group>
-    <b-form-group label="End Time" label-for="end-time">
+    <b-form-group
+      label="End Time"
+      label-for="end-time"
+      :invalid-feedback="getValidationFeedback('endTime')"
+      :state="getValidationState('endTime')"
+    >
       <datetime
         id="end-time"
         type="datetime"
         :value="endTimeAsString"
-        input-class="form-control"
+        :input-class="{
+          'form-control': true,
+          'is-invalid': getValidationState('endTime')
+        }"
         :format="{
           year: 'numeric',
           month: '2-digit',
@@ -60,15 +73,17 @@
         @input="data.endTime = stringToDate($event)"
       ></datetime>
     </b-form-group>
-    <b-form-group label="Queues" label-for="queues"
+    <b-form-group
+      label="Queues"
+      label-for="queues"
       :invalid-feedback="getValidationFeedback('queueNames')"
       :state="getValidationState('queueNames')"
     >
       <b-form-checkbox-group
-      id="queues"
-      v-model="data.queueNames"
-      :options="queueNameOptions"
-      :state="getValidationState('queueNames')"
+        id="queues"
+        v-model="data.queueNames"
+        :options="queueNameOptions"
+        :state="getValidationState('queueNames')"
       />
     </b-form-group>
   </b-form>
@@ -96,6 +111,9 @@ export default {
       nameInputBegins: false
     };
   },
+  created() {
+    this.$on("input", this.valuesChanged);
+  },
   computed: {
     startTimeAsString() {
       return this.data.startTime.toISOString();
@@ -104,13 +122,13 @@ export default {
       return this.data.endTime.toISOString();
     },
     nameValidationFeedback() {
-      return this.getValidationFeedback('reservationName');
+      return this.getValidationFeedback("reservationName");
     },
     nameValidationState() {
       if (this.nameInputBegins === false) {
         return null;
       }
-      return this.getValidationState('reservationName');
+      return this.getValidationState("reservationName");
     },
     queueNameOptions() {
       return this.queues.slice().sort();
@@ -129,9 +147,9 @@ export default {
     valuesChanged() {
       const validationResults = this.data.validate();
       if (Object.keys(validationResults).length === 0) {
-        this.$emit('valid');
+        this.$emit("valid");
       } else {
-        this.$emit('invalid');
+        this.$emit("invalid");
       }
     }
   }
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
index 2b2671b..2415291 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
@@ -11,8 +11,14 @@
         <compute-resource-reservation-editor
           v-model="newReservation"
           :queues="queues"
-          @valid="newReservationValid = true"
-          @invalid="newReservationValid = false"
+          @valid="
+            newReservationValid = true;
+            validate();
+          "
+          @invalid="
+            newReservationValid = false;
+            validate();
+          "
         />
         <div class="row">
           <div class="col">
@@ -48,6 +54,7 @@
             v-if="!readonly"
             class="action-link"
             @click="toggleDetails(data)"
+              :disabled="isReservationInvalid(data.item.key)"
           >
             Edit
             <i class="fa fa-edit" aria-hidden="true"></i>
@@ -67,8 +74,15 @@
               :value="row.item"
               @input="updatedReservation"
               :queues="queues"
+              @valid="removeInvalidReservation(row.item.key)"
+              @invalid="recordInvalidReservation(row.item.key)"
             />
-            <b-button size="sm" @click="toggleDetails(row)">Close</b-button>
+            <b-button
+              size="sm"
+              @click="toggleDetails(row)"
+              :disabled="isReservationInvalid(row.item.key)"
+              >Close</b-button
+            >
           </b-card>
         </template>
       </b-table>
@@ -108,7 +122,8 @@ export default {
       showingDetails: {},
       showNewItemEditor: false,
       newReservation: null,
-      newReservationValid: false
+      newReservationValid: false,
+      invalidReservations: [] // list of ComputeResourceReservation.key
     };
   },
   computed: {
@@ -153,22 +168,31 @@ export default {
     },
     isSaveDisabled() {
       return !this.newReservationValid;
+    },
+    valid() {
+      return (
+        (!this.showNewItemEditor || this.newReservationValid) &&
+        this.invalidReservations.length === 0
+      );
     }
   },
   created() {},
   methods: {
     updatedReservation(newValue) {
-      const reservationIndex = this.reservations.findIndex(r => r.key === newValue.key);
+      const reservationIndex = this.reservations.findIndex(
+        r => r.key === newValue.key
+      );
       this.$emit("updated", newValue, reservationIndex);
     },
     toggleDetails(row) {
       row.toggleDetails();
-      this.showingDetails[row.item.key] = !this.showingDetails[
-        row.item.key
-      ];
+      this.showingDetails[row.item.key] = !this.showingDetails[row.item.key];
     },
     deleteReservation(reservation) {
-      const reservationIndex = this.reservations.findIndex(r => r.key === reservation.key);
+      const reservationIndex = this.reservations.findIndex(
+        r => r.key === reservation.key
+      );
+      this.removeInvalidReservation(reservation.key);
       this.$emit("deleted", reservationIndex);
     },
     addNewReservation() {
@@ -183,6 +207,29 @@ export default {
     },
     cancelNewReservation() {
       this.showNewItemEditor = false;
+    },
+    recordInvalidReservation(reservationKey) {
+      if (this.invalidReservations.indexOf(reservationKey) < 0) {
+        this.invalidReservations.push(reservationKey);
+      }
+      this.validate();
+    },
+    removeInvalidReservation(reservationKey) {
+      const index = this.invalidReservations.indexOf(reservationKey);
+      if (index >= 0) {
+        this.invalidReservations.splice(index, 1);
+      }
+      this.validate();
+    },
+    isReservationInvalid(reservationKey) {
+      return this.invalidReservations.indexOf(reservationKey) >= 0;
+    },
+    validate() {
+      if (this.valid) {
+        this.$emit("valid");
+      } else {
+        this.$emit("invalid");
+      }
     }
   }
 };
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js b/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
index f922b5f..de985a4 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
@@ -1,118 +1,145 @@
-import BaseEnum from './BaseEnum'
+import BaseEnum from "./BaseEnum";
 
 export default class BaseModel {
-
-    /**
-     * Create and optionally populate fields of a model instance.
-     * - fields: an Array of field definitions. Each field definition can either
-     *   be just the name of the field as a string, or an object with the
-     *   following properties:
-     *   - name (required)
-     *   - type (required: one of 'string', 'boolean', 'number', 'date', or a class reference)
-     *   - list (optional, boolean)
-     *   - default (optional, the default value to be used, if not specified then null is used)
-     * - data: a data object, typically a deserialized JSON response
-     */
-    constructor(fields, data={}){
-        fields.forEach(fieldDefinition => {
-            if (typeof fieldDefinition === 'string') {
-                this[fieldDefinition] = this.convertSimpleField(data[fieldDefinition], null);
-            } else { // fieldDefinition must be an object
-                let fieldName = fieldDefinition.name;
-                let fieldType = fieldDefinition.type;
-                let fieldIsList = typeof fieldDefinition.list !== 'undefined' ? fieldDefinition.list : false;
-                let fieldDefault = typeof fieldDefinition.default !== 'undefined' ? this.getDefaultValue(fieldDefinition.default) : null;
-                let fieldValue = data[fieldName];
-                if (fieldIsList) {
-                    this[fieldName] = fieldValue ? fieldValue.map(item => this.convertField(fieldType, item, fieldDefault)) : fieldDefault;
-                } else {
-                    this[fieldName] = this.convertField(fieldType, fieldValue, fieldDefault);
-                }
-            }
-        });
-    }
-
-    convertField(fieldType, fieldValue, fieldDefault) {
-        if (fieldValue === null || typeof fieldValue === 'undefined') {
-            return fieldDefault;
-        } else if (fieldType === 'string' || fieldType === 'boolean' || fieldType === 'number') {
-            return this.convertSimpleField(fieldValue, fieldDefault);
-        } else if (fieldType === 'date') {
-            return this.convertDateField(fieldValue, fieldDefault);
-        } else if (typeof fieldType === 'function') {
-            // Assume that it is another BaseModel class
-            return this.convertModelField(fieldType, fieldValue, fieldDefault);
+  /**
+   * Create and optionally populate fields of a model instance.
+   * - fields: an Array of field definitions. Each field definition can either
+   *   be just the name of the field as a string, or an object with the
+   *   following properties:
+   *   - name (required)
+   *   - type (required: one of 'string', 'boolean', 'number', 'date', or a class reference)
+   *   - list (optional, boolean)
+   *   - default (optional, the default value to be used, if not specified then null is used)
+   * - data: a data object, typically a deserialized JSON response
+   */
+  constructor(fields, data = {}) {
+    fields.forEach(fieldDefinition => {
+      if (typeof fieldDefinition === "string") {
+        this[fieldDefinition] = this.convertSimpleField(
+          data[fieldDefinition],
+          null
+        );
+      } else {
+        // fieldDefinition must be an object
+        let fieldName = fieldDefinition.name;
+        let fieldType = fieldDefinition.type;
+        let fieldIsList =
+          typeof fieldDefinition.list !== "undefined"
+            ? fieldDefinition.list
+            : false;
+        let fieldDefault =
+          typeof fieldDefinition.default !== "undefined"
+            ? this.getDefaultValue(fieldDefinition.default)
+            : null;
+        let fieldValue = data[fieldName];
+        if (fieldIsList) {
+          this[fieldName] = fieldValue
+            ? fieldValue.map(item =>
+                this.convertField(fieldType, item, fieldDefault)
+              )
+            : fieldDefault;
+        } else {
+          this[fieldName] = this.convertField(
+            fieldType,
+            fieldValue,
+            fieldDefault
+          );
         }
-    }
+      }
+    });
+  }
 
-    convertSimpleField(fieldValue, fieldDefault) {
-        return typeof fieldValue !== 'undefined' ? fieldValue : fieldDefault;
+  convertField(fieldType, fieldValue, fieldDefault) {
+    if (fieldValue === null || typeof fieldValue === "undefined") {
+      return fieldDefault;
+    } else if (
+      fieldType === "string" ||
+      fieldType === "boolean" ||
+      fieldType === "number"
+    ) {
+      return this.convertSimpleField(fieldValue, fieldDefault);
+    } else if (fieldType === "date") {
+      return this.convertDateField(fieldValue, fieldDefault);
+    } else if (typeof fieldType === "function") {
+      // Assume that it is another BaseModel class
+      return this.convertModelField(fieldType, fieldValue, fieldDefault);
     }
+  }
 
-    convertDateField(fieldValue, fieldDefault) {
-        return typeof fieldValue !== 'undefined' ? new Date(fieldValue) : fieldDefault;
-    }
+  convertSimpleField(fieldValue, fieldDefault) {
+    return typeof fieldValue !== "undefined" ? fieldValue : fieldDefault;
+  }
 
-    convertModelField(modelClass, fieldValue, fieldDefault) {
-        if (typeof fieldValue !== 'undefined') {
-            if (modelClass.prototype instanceof BaseEnum) {
-                // When cloning the fieldValue is an enum instance
-                if (fieldValue instanceof BaseEnum){
-                    return fieldValue;
-                }
-                let enumValue = null;
-                if (typeof fieldValue === 'string') {
-                    // convert by name if type is string
-                    enumValue = modelClass.byName(fieldValue);
-                } else {
-                    // Otherwise it is an integer that we need to convert to enum
-                    enumValue = modelClass.byValue(fieldValue);
-                }
-                if (!enumValue) {
-                    // enum wasn't found, construct an enum instance from the value
-                    return new BaseEnum(`Unknown value: ${fieldValue}`, fieldValue);
-                } else {
-                    return enumValue;
-                }
-            } else if (fieldValue instanceof modelClass) {
-                // No conversion necessary, just return the fieldValue
-                return fieldValue;
-            } else {
-                return new modelClass(fieldValue);
-            }
-        }
-        return fieldDefault;
-    }
+  convertDateField(fieldValue, fieldDefault) {
+    return typeof fieldValue !== "undefined"
+      ? new Date(fieldValue)
+      : fieldDefault;
+  }
 
-    getDefaultValue(fieldDefault) {
-        if (typeof fieldDefault === 'function') {
-            return fieldDefault();
+  convertModelField(modelClass, fieldValue, fieldDefault) {
+    if (typeof fieldValue !== "undefined") {
+      if (modelClass.prototype instanceof BaseEnum) {
+        // When cloning the fieldValue is an enum instance
+        if (fieldValue instanceof BaseEnum) {
+          return fieldValue;
+        }
+        let enumValue = null;
+        if (typeof fieldValue === "string") {
+          // convert by name if type is string
+          enumValue = modelClass.byName(fieldValue);
+        } else {
+          // Otherwise it is an integer that we need to convert to enum
+          enumValue = modelClass.byValue(fieldValue);
+        }
+        if (!enumValue) {
+          // enum wasn't found, construct an enum instance from the value
+          return new BaseEnum(`Unknown value: ${fieldValue}`, fieldValue);
         } else {
-            return fieldDefault;
+          return enumValue;
         }
+      } else if (fieldValue instanceof modelClass) {
+        // No conversion necessary, just return the fieldValue
+        return fieldValue;
+      } else {
+        return new modelClass(fieldValue);
+      }
     }
+    return fieldDefault;
+  }
 
-    static defaultNewInstance(classRef) {
-        return () => new classRef();
+  getDefaultValue(fieldDefault) {
+    if (typeof fieldDefault === "function") {
+      return fieldDefault();
+    } else {
+      return fieldDefault;
     }
+  }
 
-    /**
-     * Override to provide validation. If there are validation errors this
-     * method should return a dictionary where keys are property names and
-     * values are an array of error messages.
-     */
-    validate() {
-        return null;
-    }
+  static defaultNewInstance(classRef) {
+    return () => new classRef();
+  }
 
-    isEmpty(value) {
-        return value === null || (typeof value === 'string' && value.trim() === '');
-    }
+  /**
+   * Override to provide validation. If there are validation errors this
+   * method should return a dictionary where keys are property names and
+   * values are an array of error messages.
+   */
+  validate() {
+    return null;
+  }
 
-    /**
-     * Return a fully deep cloned instance of this instance.
-     */
-    clone() {
-        return new this.constructor(this);
-    }
+  isEmpty(value) {
+    return (
+      value === null ||
+      (typeof value === "string" && value.trim() === "") ||
+      (value instanceof Array && value.length === 0)
+    );
+  }
+
+  /**
+   * Return a fully deep cloned instance of this instance.
+   */
+  clone() {
+    return new this.constructor(this);
+  }
 }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
index 9a437b4..c4a8921 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
@@ -12,13 +12,12 @@ const FIELDS = [
   {
     name: "startTime",
     type: Date,
-    default: () => new Date(),
-
+    default: () => new Date()
   },
   {
     name: "endTime",
     type: Date,
-    default: () => new Date(),
+    default: () => new Date()
   }
 ];
 
@@ -36,6 +35,12 @@ export default class ComputeResourceReservation extends BaseModel {
       validationResults["reservationName"] =
         "Please provide the name of this reservation.";
     }
+    if (this.startTime > this.endTime) {
+      validationResults["endTime"] = "End time must be later than start time.";
+    }
+    if (this.isEmpty(this.queueNames)) {
+      validationResults["queueNames"] = "Please select at least one queue.";
+    }
     return validationResults;
   }
 }


[airavata-django-portal] 04/09: AIRAVATA-3216: Remove dynamic sorting, but sort new reservations by start time

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 6f9ed0d52323feda6af5510a7e5d3c8c685e67d5
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Mar 21 11:27:32 2020 -0400

    AIRAVATA-3216: Remove dynamic sorting, but sort new reservations by start time
---
 .../components/admin/group_resource_preferences/ComputePreference.vue  | 1 +
 .../group_resource_preferences/ComputeResourceReservationList.vue      | 3 ---
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index 5ad366e..bff050e 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -478,6 +478,7 @@ export default {
     },
     addReservation(reservation) {
       this.data.reservations.push(reservation);
+      this.data.reservations.sort((a, b) => a.startTime < b.startTime ? -1 : 1);
     },
     deleteReservation(reservation) {
       const reservationIndex = this.data.reservations.findIndex(
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
index 8c23922..9054f9c 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationList.vue
@@ -51,7 +51,6 @@
         hover
         :fields="fields"
         :items="slotProps.items"
-        sort-by="startTime"
       >
         <template slot="reservationName" slot-scope="data">
           {{ data.value }}
@@ -154,14 +153,12 @@ export default {
         {
           label: "Start Time",
           key: "startTime",
-          sortable: true,
           formatter: value =>
             utils.dateFormatters.dateTimeInMinutesWithTimeZone.format(value)
         },
         {
           label: "End Time",
           key: "endTime",
-          sortable: true,
           formatter: value =>
             utils.dateFormatters.dateTimeInMinutesWithTimeZone.format(value)
         },


[airavata-django-portal] 07/09: AIRAVATA-3126 Fix save/cancel to bottom; summarize reservations

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit b54d480c7e59e38ee6b65414eeb33fb46c85aff3
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Mar 21 12:28:38 2020 -0400

    AIRAVATA-3126 Fix save/cancel to bottom; summarize reservations
---
 .../ComputePreference.vue                          |   6 +-
 .../ComputeResourceReservationsSummary.vue         |  45 +++++++++
 .../GroupComputeResourcePreference.vue             | 108 ++++++++++++++++-----
 .../js/models/ComputeResourceReservation.js        |   8 ++
 4 files changed, 137 insertions(+), 30 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index bff050e..bea36f0 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="has-fixed-footer">
     <div class="row">
       <div class="col">
         <h1 class="h4 mb-4">
@@ -161,8 +161,7 @@
         </div>
       </div>
     </div>
-    <div class="row">
-      <div class="col d-flex justify-content-end">
+    <div class="fixed-footer">
         <b-button variant="primary" @click="save" :disabled="!valid"
           >Save</b-button
         >
@@ -172,7 +171,6 @@
         <b-button class="ml-2" variant="secondary" @click="cancel"
           >Cancel</b-button
         >
-      </div>
     </div>
   </div>
 </template>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationsSummary.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationsSummary.vue
new file mode 100644
index 0000000..0cd71f2
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputeResourceReservationsSummary.vue
@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <div v-if="expiredReservations.length > 0">
+      {{ expiredReservations.length }} expired
+    </div>
+    <div v-if="activeReservations.length > 0">
+      {{ activeReservations.length }} active ({{
+        activeReservationNames.join(", ")
+      }})
+    </div>
+    <div v-if="upcomingReservations.length > 0">
+      {{ upcomingReservations.length }} upcoming
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "compute-resource-reservations-summary",
+  props: {
+    reservations: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    expiredReservations() {
+      return this.reservations
+        ? this.reservations.filter(r => r.isExpired)
+        : [];
+    },
+    activeReservations() {
+      return this.reservations ? this.reservations.filter(r => r.isActive) : [];
+    },
+    activeReservationNames() {
+      return this.activeReservations.map(r => r.reservationName);
+    },
+    upcomingReservations() {
+      return this.reservations
+        ? this.reservations.filter(r => r.isUpcoming)
+        : [];
+    }
+  }
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/GroupComputeResourcePreference.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/GroupComputeResourcePreference.vue
index 7bca11c..1da5d22 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/GroupComputeResourcePreference.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/GroupComputeResourcePreference.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="has-fixed-footer">
     <div class="row">
       <div class="col">
         <h1 class="h4 mb-4">{{ title }}</h1>
@@ -10,11 +10,23 @@
         <div class="card">
           <div class="card-body">
             <b-form-group label="Name" label-for="profile-name">
-              <b-form-input id="profile-name" type="text" v-model="data.groupResourceProfileName" required placeholder="Name of this Group Resource Profile">
+              <b-form-input
+                id="profile-name"
+                type="text"
+                v-model="data.groupResourceProfileName"
+                required
+                placeholder="Name of this Group Resource Profile"
+              >
               </b-form-input>
             </b-form-group>
-            <b-form-group label="Default SSH Credential" label-for="default-credential-store-token">
-              <ssh-credential-selector id="default-credential-store-token" v-model="data.defaultCredentialStoreToken">
+            <b-form-group
+              label="Default SSH Credential"
+              label-for="default-credential-store-token"
+            >
+              <ssh-credential-selector
+                id="default-credential-store-token"
+                v-model="data.defaultCredentialStoreToken"
+              >
               </ssh-credential-selector>
             </b-form-group>
             <share-button ref="shareButton" :entity-id="id" />
@@ -22,30 +34,57 @@
         </div>
       </div>
     </div>
-    <list-layout :items="data.computePreferences" title="Compute Preferences" new-item-button-text="New Compute Preference"
-      @add-new-item="createComputePreference">
+    <list-layout
+      :items="data.computePreferences"
+      title="Compute Preferences"
+      new-item-button-text="New Compute Preference"
+      @add-new-item="createComputePreference"
+    >
       <template slot="item-list" slot-scope="slotProps">
-
-        <b-table hover :fields="computePreferencesFields" :items="slotProps.items" sort-by="computeResourceId">
+        <b-table
+          hover
+          :fields="computePreferencesFields"
+          :items="slotProps.items"
+          sort-by="computeResourceId"
+        >
           <template slot="policy" slot-scope="row">
-            <compute-resource-policy-summary :compute-resource-id="row.item.computeResourceId" :group-resource-profile="data" />
+            <compute-resource-policy-summary
+              :compute-resource-id="row.item.computeResourceId"
+              :group-resource-profile="data"
+            />
+          </template>
+          <template slot="reservations" slot-scope="row">
+            <compute-resource-reservations-summary :reservations="row.value" />
           </template>
           <template slot="action" slot-scope="row">
-            <router-link class="action-link" :to="{
+            <router-link
+              class="action-link"
+              :to="{
                 name: 'compute_preference',
                 params: {
                   value: row.item,
                   id: id,
                   host_id: row.item.computeResourceId,
                   groupResourceProfile: data,
-                  computeResourcePolicy: data.getComputeResourcePolicy(row.item.computeResourceId),
-                  batchQueueResourcePolicies: data.getBatchQueueResourcePolicies(row.item.computeResourceId)
+                  computeResourcePolicy: data.getComputeResourcePolicy(
+                    row.item.computeResourceId
+                  ),
+                  batchQueueResourcePolicies: data.getBatchQueueResourcePolicies(
+                    row.item.computeResourceId
+                  )
                 }
-              }">
+              }"
+            >
               Edit
               <i class="fa fa-edit" aria-hidden="true"></i>
             </router-link>
-            <a href="#" class="action-link text-danger" @click.prevent="removeComputePreference(row.item.computeResourceId)">
+            <a
+              href="#"
+              class="action-link text-danger"
+              @click.prevent="
+                removeComputePreference(row.item.computeResourceId)
+              "
+            >
               Delete
               <i class="fa fa-trash" aria-hidden="true"></i>
             </a>
@@ -53,15 +92,26 @@
         </b-table>
       </template>
     </list-layout>
-    <div class="row">
-      <div class="col d-flex justify-content-end">
-        <b-button variant="primary" @click="saveGroupResourceProfile">Save</b-button>
-        <b-button v-if="id" class="ml-2" variant="danger" @click="removeGroupResourceProfile">Delete</b-button>
-        <b-button class="ml-2" variant="secondary" @click="cancel">Cancel</b-button>
-      </div>
+    <div class="fixed-footer">
+      <b-button variant="primary" @click="saveGroupResourceProfile"
+        >Save</b-button
+      >
+      <b-button
+        v-if="id"
+        class="ml-2"
+        variant="danger"
+        @click="removeGroupResourceProfile"
+        >Delete</b-button
+      >
+      <b-button class="ml-2" variant="secondary" @click="cancel"
+        >Cancel</b-button
+      >
     </div>
-    <compute-resources-modal ref="modalSelectComputeResource" @selected="onSelectComputeResource"
-      :excluded-resource-ids="excludedComputeResourceIds" />
+    <compute-resources-modal
+      ref="modalSelectComputeResource"
+      @selected="onSelectComputeResource"
+      :excluded-resource-ids="excludedComputeResourceIds"
+    />
   </div>
 </template>
 
@@ -69,6 +119,7 @@
 import { components as comps, layouts } from "django-airavata-common-ui";
 import { models, services } from "django-airavata-api";
 import ComputeResourcePolicySummary from "./ComputeResourcePolicySummary.vue";
+import ComputeResourceReservationsSummary from "./ComputeResourceReservationsSummary.vue";
 import ComputeResourcesModal from "../ComputeResourcesModal.vue";
 import SSHCredentialSelector from "../../credentials/SSHCredentialSelector.vue";
 
@@ -88,9 +139,9 @@ export default {
   mounted: function() {
     if (this.id) {
       if (!this.value.groupResourceProfileId) {
-        services.GroupResourceProfileService
-          .retrieve({ lookup: this.id })
-          .then(grp => (this.data = grp));
+        services.GroupResourceProfileService.retrieve({ lookup: this.id }).then(
+          grp => (this.data = grp)
+        );
       }
     }
   },
@@ -119,6 +170,10 @@ export default {
           key: "policy" // custom rendering
         },
         {
+          label: "Reservations",
+          key: "reservations" // custom rendering
+        },
+        {
           label: "Action",
           key: "action"
         }
@@ -131,7 +186,8 @@ export default {
     "list-layout": layouts.ListLayout,
     ComputeResourcePolicySummary,
     ComputeResourcesModal,
-    "ssh-credential-selector": SSHCredentialSelector
+    "ssh-credential-selector": SSHCredentialSelector,
+    ComputeResourceReservationsSummary
   },
   computed: {
     excludedComputeResourceIds() {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
index 27d15aa..9d3110b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
@@ -54,4 +54,12 @@ export default class ComputeResourceReservation extends BaseModel {
     const now = new Date();
     return now > this.endTime;
   }
+  get isActive() {
+    const now = new Date();
+    return this.startTime < now && now < this.endTime;
+  }
+  get isUpcoming() {
+    const now = new Date();
+    return now < this.startTime;
+  }
 }


[airavata-django-portal] 05/09: AIRAVATA-3126 remove parts of hour from reservation default start/end time

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 4afb2c9416430825f9c0a47de312cd7699bf6d95
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Sat Mar 21 11:34:38 2020 -0400

    AIRAVATA-3126 remove parts of hour from reservation default start/end time
---
 .../js/models/ComputeResourceReservation.js                   | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
index 2303ad7..27d15aa 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputeResourceReservation.js
@@ -1,6 +1,13 @@
 import BaseModel from "./BaseModel";
 import uuidv4 from "uuid/v4";
 
+function currentTimeTopOfHour() {
+  const d = new Date();
+  d.setMinutes(0);
+  d.setSeconds(0);
+  d.setMilliseconds(0);
+  return d;
+}
 const FIELDS = [
   "reservationId",
   "reservationName",
@@ -12,12 +19,12 @@ const FIELDS = [
   {
     name: "startTime",
     type: Date,
-    default: () => new Date()
+    default: () => currentTimeTopOfHour()
   },
   {
     name: "endTime",
     type: Date,
-    default: () => new Date()
+    default: () => currentTimeTopOfHour()
   }
 ];
 


[airavata-django-portal] 09/09: Merge branch 'AIRAVATA-3126--Implement-compute-resource-reservation-addition' into develop

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 91e431889394b37bd21eb70e1c5afeae82cd8f39
Merge: ea01ec8 f322b37
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Mar 25 16:06:53 2020 -0400

    Merge branch 'AIRAVATA-3126--Implement-compute-resource-reservation-addition' into develop

 .../appcatalog/groupresourceprofile/ttypes.py      | 217 +++++++++++++++---
 .../ComputePreference.vue                          | 119 +++++++---
 .../ComputeResourceReservationEditor.vue           | 158 +++++++++++++
 .../ComputeResourceReservationList.vue             | 251 +++++++++++++++++++++
 .../ComputeResourceReservationsSummary.vue         |  45 ++++
 .../GroupComputeResourcePreference.vue             | 108 ++++++---
 .../gatewayprofile/StoragePreferenceList.vue       |   2 +-
 django_airavata/apps/api/serializers.py            |   9 +-
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../django_airavata_api/js/models/BaseModel.js     | 229 ++++++++++---------
 .../js/models/ComputeResourceReservation.js        |  65 ++++++
 .../js/models/GroupComputeResourcePreference.js    |   8 +-
 django_airavata/settings_local.py.ide              |   6 +-
 .../static/common/js/components/DeleteButton.vue   |   6 +-
 .../static/common/js/components/HumanDate.vue      |   2 +-
 .../static/common/js/layouts/ListLayout.vue        |   2 +
 django_airavata/static/common/js/utils.js          |  25 +-
 17 files changed, 1047 insertions(+), 207 deletions(-)