KNOX-1040 - Added modification support: delete, discard changes, save changes


Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/cd336759
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/cd336759
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/cd336759

Branch: refs/heads/master
Commit: cd336759970caaa6051e28b9204dfe46034c01de
Parents: 5b57a51
Author: Phil Zampino <[email protected]>
Authored: Fri Feb 2 22:01:48 2018 -0500
Committer: Phil Zampino <[email protected]>
Committed: Wed Feb 7 09:43:00 2018 -0500

----------------------------------------------------------------------
 gateway-admin-ui/package.json                   |   2 +-
 .../src/app/resource-detail/descriptor.ts       |  14 +
 .../resource-detail.component.html              | 144 ++++++++-
 .../resource-detail.component.ts                | 305 ++++++++++++++++---
 .../src/app/resource/resource.component.ts      |   5 +
 .../src/app/resource/resource.service.ts        |  67 ++--
 .../applications/admin-ui/app/index.html        |   2 +-
 .../app/inline.1b8a794b441ffd968489.bundle.js   |   1 -
 .../app/inline.1bf9c629c91002a65d30.bundle.js   |   1 +
 .../app/main.10e7c17d5707ac382dbb.bundle.js     |   1 -
 .../app/main.4689931d0eaf6a8f51c9.bundle.js     |   1 +
 .../apache/knox/gateway/GatewayMessages.java    |   3 +
 .../topology/impl/DefaultTopologyService.java   |  98 +++++-
 .../DefaultRemoteConfigurationMonitor.java      |   5 +
 .../org/apache/knox/gateway/util/KnoxCLI.java   |  12 +-
 .../service/admin/TopologiesResource.java       |  88 ++++--
 .../monitor/RemoteConfigurationMonitor.java     |   5 +
 .../gateway/GatewayAdminTopologyFuncTest.java   |   9 -
 18 files changed, 644 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/package.json
----------------------------------------------------------------------
diff --git a/gateway-admin-ui/package.json b/gateway-admin-ui/package.json
index 9f6548c..8fbfeb5 100644
--- a/gateway-admin-ui/package.json
+++ b/gateway-admin-ui/package.json
@@ -26,7 +26,7 @@
     "ng2-ace-editor": "0.3.3",
     "ng2-bs3-modal": "^0.13.0",
     "ts-helpers": "^1.1.1",
-    "jquery": "^1.12.4",
+    "jquery": ">=3.0.0",
     "js-yaml": "^3.10.0",
     "popper.js": "^1.12.9"
   },

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/src/app/resource-detail/descriptor.ts
----------------------------------------------------------------------
diff --git a/gateway-admin-ui/src/app/resource-detail/descriptor.ts 
b/gateway-admin-ui/src/app/resource-detail/descriptor.ts
index c407f96..998265a 100644
--- a/gateway-admin-ui/src/app/resource-detail/descriptor.ts
+++ b/gateway-admin-ui/src/app/resource-detail/descriptor.ts
@@ -25,6 +25,8 @@ export class Descriptor {
     providerConfig: string;
     services: Service[];
 
+    private dirty: boolean = false;
+
     getServiceParamKeys(service: Service): string[] {
         let result = [];
         for(let key in service.params){
@@ -40,7 +42,19 @@ export class Descriptor {
     }
 
     setProviderConfig(providerConfigRef: string) {
+      console.debug('Descriptor --> setProviderConfig() --> ' + 
providerConfigRef);
+      if (providerConfigRef !== this.providerConfig) {
         this.providerConfig = providerConfigRef;
+        this.setDirty();
+      }
+    }
+
+    setDirty() {
+        this.dirty = true;
+    }
+
+    public isDirty(): boolean {
+        return this.dirty;
     }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/src/app/resource-detail/resource-detail.component.html
----------------------------------------------------------------------
diff --git 
a/gateway-admin-ui/src/app/resource-detail/resource-detail.component.html 
b/gateway-admin-ui/src/app/resource-detail/resource-detail.component.html
index 3303341..96f6efb 100644
--- a/gateway-admin-ui/src/app/resource-detail/resource-detail.component.html
+++ b/gateway-admin-ui/src/app/resource-detail/resource-detail.component.html
@@ -10,24 +10,30 @@
     <div *ngIf="hasSelectedResource()">
       <div class="panel panel-default" *ngFor="let provider of providers">
         <span [class]="'clickable glyhpicon glyphicon-' + (provider.show ? 
'minus' : 'plus')"
-                 (click)="provider.show = !provider.show"></span>
+              (click)="provider.show = !provider.show"></span>
         <span><b>{{ provider.name }}</b></span>
-        <span class="glyphicon glyphicon-remove-sign pull-right" 
data-toggle="tooltip" [title]="'Remove ' + provider.name"></span>
+        <span class="clickable glyphicon glyphicon-remove-sign pull-right"
+              [title]="'Remove ' + provider.name"
+              (click)="onRemoveProvider(provider.name)"
+              data-toggle="tooltip"></span>
         <div *ngIf="provider.show">
           &nbsp;&nbsp;&nbsp;<b>Role</b> {{ provider.role }}<br>
           &nbsp;&nbsp;&nbsp;<b>Enabled</b> {{ provider.enabled }}<br>
           <div>
             <span>&nbsp;&nbsp;&nbsp;</span>
-            <span [class]="'clickable glyhpicon glyphicon-' + 
(provider.paramDetails ? 'minus' : 'plus')"
-                  (click)="provider.paramDetails = 
!provider.paramDetails"></span>
+            <span [class]="'clickable glyhpicon glyphicon-' + 
(provider.showParamDetails ? 'minus' : 'plus')"
+                  (click)="provider.showParamDetails = 
!provider.showParamDetails"></span>
             <span><b>Params</b></span>
-            <span class="glyphicon glyphicon-plus-sign pull-right" 
data-toggle="tooltip" title="Add Param"></span>
+            <span class="clickable glyphicon glyphicon-plus-sign pull-right" 
data-toggle="tooltip" title="Add Param"></span>
 
-            <div class="panel panel-default table-responsive" 
*ngIf="provider.paramDetails">
+            <div class="panel panel-default table-responsive" 
*ngIf="provider.showParamDetails">
               <table class="table table-sm">
                 <tr *ngFor="let key of getParamKeys(provider)">
                   <td>
-                    <span class="glyphicon glyphicon-remove-sign"></span>
+                    <span class="clickable glyphicon glyphicon-remove-sign"
+                          title="Remove Param"
+                          (click)="onRemoveProviderParam(provider, key)"
+                          data-toggle="tooltip"></span>
                   </td>
                   <td><b>{{ key }}</b></td>
                   <td>{{ provider.params[key] }}</td>
@@ -39,6 +45,35 @@
         <br><br>
       </div>
     </div>
+
+    <div> <!-- Provider Configuration Modification Buttons -->
+      <button type="button"
+              title="Remove Provider Configuration"
+              class="btn btn-default btn-sm pull-left"
+              (click)="deleteConfirmModal.open('md')"
+              data-toggle="tooltip">
+        <span class="clickable glyphicon glyphicon-trash"></span>
+      </button>
+      <span class="pull-right">
+        <button type="button"
+                title="Discard Changes"
+                class="btn btn-default btn-sm"
+                [disabled]="!changedProviders"
+                (click)="discardConfirmModal.open('md')"
+                data-toggle="tooltip">
+          <span class="glyphicon glyphicon-refresh"></span>
+        </button>
+        <span>&nbsp;</span>
+        <button type="button"
+                title="Update Provider Configuration"
+                class="btn btn-default btn-sm"
+                [disabled]="!changedProviders"
+                (click)="persistChanges()"
+                data-toggle="tooltip">
+          <span class="glyphicon glyphicon-floppy-disk"></span>
+        </button>
+      </span>
+    </div>
   </div>
 
   <!-- Descriptor Details -->
@@ -49,13 +84,19 @@
         <div class="panel panel-default col-md-12">
           <span class="col-md-4"><b>Provider Configuration</b></span>
           <span class="col-md-7">
-            <input type="text" class="col-md-sm form-control input-sm" 
[(ngModel)]="descriptor.providerConfig" id="textbox"/>
+            <input type="text"
+                   id="textbox"
+                   class="col-md-sm form-control input-sm"
+                   (input)="descriptor.setDirty()"
+                   [(ngModel)]="descriptor.providerConfig"/>
           </span>
           <span class="col-md-1 pull-left">
             <button id="chooseProviderConfig"
                     class="btn btn-default btn-xs glyphicon glyphicon-edit"
                     (click)="chooseProviderConfigModal.open(descriptor, 'sm')"
-                    type="submit">
+                    type="submit"
+                    data-toggle="tooltip"
+                    title="Choose Provider Configuration">
             </button>
           </span>
         </div>
@@ -68,25 +109,32 @@
         <span [class]="'clickable glyhpicon glyphicon-' + (showServiceDetails 
? 'minus' : 'plus')"
               (click)="showServiceDetails = !showServiceDetails"></span>
         <span><b>Services</b></span>
-        <span class="glyphicon glyphicon-plus-sign pull-right" 
data-toggle="tooltip" title="Add Service"></span>
+        <span class="clickable glyphicon glyphicon-plus-sign pull-right" 
data-toggle="tooltip" title="Add Service"></span>
       <div class="col-md-12 table-responsive" *ngIf="showServiceDetails">
         <table class="table table-striped table-sm">
           <tr *ngFor="let service of descriptor.services">
             <td>
               <div>
                 <span><b>{{ service.name }}</b></span>
-                <span class="glyphicon glyphicon-remove-sign pull-right"  
data-toggle="tooltip" [title]="'Remove ' + service.name"></span>
+                <span class="clickable glyphicon glyphicon-remove-sign 
pull-right"
+                      [title]="'Remove ' + service.name"
+                      (click)="onRemoveDescriptorService(service.name)"
+                      data-toggle="tooltip"></span>
               </div>
               <div>
                 <span [class]="'clickable glyhpicon glyphicon-' + 
(service.showParams ? 'minus' : 'plus')"
                       (click)="service.showParams = 
!service.showParams"></span>
                 <span>Params</span><span>&nbsp;</span><span class="glyphicon 
glyphicon-plus-sign" data-toggle="tooltip" title="Add param"></span>
-                <!--<span class="btn btn-default btn-xs" data-toggle="tooltip" 
title="Add param">add</span>-->
               </div>
               <div class="table-responsive" *ngIf="service.showParams">
                 <table class="table table-sm" 
*ngIf="descriptor.getServiceParamKeys(service).length > 0">
                   <tr *ngFor="let paramKey of 
descriptor.getServiceParamKeys(service)">
-                    <td width="5%"><span class="glyphicon 
glyphicon-remove-sign"></span></td>
+                    <td width="5%">
+                      <span class="clickable glyphicon glyphicon-remove-sign"
+                            title="Remove Param"
+                            
(click)="onRemoveDescriptorServiceParam(service.name, paramKey)"
+                            data-toggle="tooltip"></span>
+                    </td>
                     <td width="30%"><b>{{ paramKey }}</b></td>
                     <td width="65%" align="left">{{ 
descriptor.getServiceParamValue(service, paramKey) }}</td>
                   </tr>
@@ -100,7 +148,12 @@
               <div class="table-responsive" *ngIf="service.showUrls">
                 <table class="table table-sm" *ngIf="service.urls && 
service.urls.length > 0">
                   <tr *ngFor="let url of service.urls">
-                    <td width="5%"><span class="glyphicon 
glyphicon-remove-sign"></span></td>
+                    <td width="5%">
+                      <span class="clickable glyphicon glyphicon-remove-sign"
+                            title="Remove URL"
+                            
(click)="onRemoveDescriptorServiceURL(service.name, url)"
+                            data-toggle="tooltip"></span>
+                    </td>
                     <td width="95%">{{ url }}</td>
                   </tr>
                 </table>
@@ -125,6 +178,69 @@
         </div>
       </div>
     </div>
+
+    <div> <!-- Descriptor Modification Buttons -->
+      <button type="button"
+              title="Remove Descriptor"
+              class="btn btn-default btn-sm pull-left"
+              (click)="deleteConfirmModal.open('md')"
+              data-toggle="tooltip">
+        <span class="clickable glyphicon glyphicon-trash"></span>
+      </button>
+      <span class="pull-right">
+        <button type="button"
+                title="Discard Changes"
+                class="btn btn-default btn-sm"
+                [disabled]="!descriptor.isDirty()"
+                (click)="discardConfirmModal.open('md')"
+                data-toggle="tooltip">
+          <span class="glyphicon glyphicon-refresh"></span>
+        </button>
+        <span>&nbsp;</span>
+        <button type="button"
+                title="Update Descriptor"
+                class="btn btn-default btn-sm"
+                [disabled]="!descriptor.isDirty()"
+                (click)="persistChanges()"
+                data-toggle="tooltip">
+          <span class="glyphicon glyphicon-floppy-disk"></span>
+        </button>
+      </span>
+    </div>
+  </div>
+
+  <div> <!-- Confirmation Modal Dialogs -->
+    <bs-modal (onClose)="deleteResource()" #deleteConfirmModal>
+      <bs-modal-header [showDismiss]="true">
+        <h4 class="modal-title">Deleting {{getTitleSubject()}}</h4>
+      </bs-modal-header>
+      <bs-modal-body>Are you sure you want to delete 
<b>{{resourceService.getResourceDisplayName(resource)}}</b>?</bs-modal-body>
+      <bs-modal-footer>
+        <button type="button"
+                class="btn btn-default btn-sm"
+                data-dismiss="deleteConfirmModal"
+                (click)="deleteConfirmModal.dismiss()">Cancel</button>
+        <button type="button"
+                class="btn btn-primary btn-sm"
+                (click)="deleteConfirmModal.close()">Ok</button>
+      </bs-modal-footer>
+    </bs-modal>
+
+    <bs-modal (onClose)="discardChanges()" #discardConfirmModal>
+      <bs-modal-header [showDismiss]="true">
+        <h4 class="modal-title">Discard {{getTitleSubject()}} Changes</h4>
+      </bs-modal-header>
+      <bs-modal-body>Are you sure you want to discard 
<b>{{resourceService.getResourceDisplayName(resource)}}</b> 
changes?</bs-modal-body>
+      <bs-modal-footer>
+        <button type="button"
+                class="btn btn-default btn-sm"
+                data-dismiss="revertConfirmModal"
+                (click)="discardConfirmModal.dismiss()">Cancel</button>
+        <button type="button"
+                class="btn btn-primary btn-sm"
+                (click)="discardConfirmModal.close()">Ok</button>
+      </bs-modal-footer>
+    </bs-modal>
   </div>
 </div>
 

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/src/app/resource-detail/resource-detail.component.ts
----------------------------------------------------------------------
diff --git 
a/gateway-admin-ui/src/app/resource-detail/resource-detail.component.ts 
b/gateway-admin-ui/src/app/resource-detail/resource-detail.component.ts
index 3a85574..5d9e2e7 100644
--- a/gateway-admin-ui/src/app/resource-detail/resource-detail.component.ts
+++ b/gateway-admin-ui/src/app/resource-detail/resource-detail.component.ts
@@ -26,6 +26,7 @@ import 'brace/theme/monokai';
 import 'brace/mode/xml';
 
 import { ProviderConfigSelectorComponent } from 
"../provider-config-selector/provider-config-selector.component";
+import { ResourceTypesService } from "../resourcetypes/resourcetypes.service";
 
 
 @Component({
@@ -47,46 +48,30 @@ export class ResourceDetailComponent implements OnInit {
   resourceContent: string;
 
   providers: Array<ProviderConfig>;
+  changedProviders: Array<ProviderConfig>;
 
   descriptor: Descriptor;
-
-  availableProviderConfigs: Resource[];
+  changedDescriptor: Descriptor;
 
   @ViewChild('choosePC')
   chooseProviderConfigModal: ProviderConfigSelectorComponent;
 
-  constructor(private resourceService: ResourceService) {
+  constructor(private resourceService: ResourceService, private 
resourceTypesService: ResourceTypesService) {
   }
 
   ngOnInit() {
-      this.resourceService.getResources('Provider Configurations').then(pcs => 
{
-          this.availableProviderConfigs = pcs;
-      });
-
       this.resourceService.selectedResourceType$.subscribe(type => 
this.setResourceType(type));
       this.resourceService.selectedResource$.subscribe(value => 
this.setResource(value));
   }
 
-  get self() {
-      return this;
-  }
-
   setResourceType(resType: string) {
-      if (resType !== this.resourceType) {
-
-        if (resType === 'Descriptors') {
-          // Update the available provider configurations if we're dealing 
with descriptors
-          this.resourceService.getResources("Provider 
Configurations").then(result => this.availableProviderConfigs = result);
-        }
-
-        // Clear the current resource details
-        if (this.resource) {this.resource.name = '';} // This clears the 
details title when the type context changes
-        this.resource = ResourceDetailComponent.emptyResource;
-        this.providers = null;
-        this.descriptor = ResourceDetailComponent.emptyDescriptor;
-        this.resourceContent = ''; // Clear the content area
-        this.resourceType = resType;
-      }
+    // Clear the current resource details
+    if (this.resource) {this.resource.name = '';} // This clears the details 
title when the type context changes
+    this.resource = ResourceDetailComponent.emptyResource;
+    this.providers = null;
+    this.descriptor = ResourceDetailComponent.emptyDescriptor;
+    this.resourceContent = ''; // Clear the content area
+    this.resourceType = resType;
   }
 
   setResource(res: Resource) {
@@ -125,21 +110,30 @@ export class ResourceDetailComponent implements OnInit {
             } else if (res.name.endsWith('xml')) {
                 // Parse the XML representation
                 parseString(this.resourceContent, (err, result) => {
+                    // Parsing the XML is a bit less straight-forward
                     let tempProviders = new Array<ProviderConfig>();
                     result['gateway'].provider.forEach(entry => {
-                       let providerConfig: ProviderConfig = entry;
-                       let params = {};
-                       entry.param.forEach(param => {
-                           params[param.name] = param.value;
-                       });
-                       providerConfig.params = params;
+                       let providerConfig: ProviderConfig = new 
ProviderConfig();
+                       providerConfig.role = entry.role[0];
+                       providerConfig.name = entry.name[0];
+                       providerConfig.enabled = entry.enabled[0];
+
+                       // There may not be params
+                       if (entry.param) {
+                         let params = {};
+                         for (let i = 0; i < entry.param.length; i++) {
+                           let param = entry.param[i];
+                           params[param.name[0]] = param.value[0];
+                         }
+                         providerConfig.params = params;
+                       }
                        tempProviders.push(providerConfig);
                     });
                     this.providers = tempProviders;
                 });
             }
           } catch (e) {
-            console.error('Error parsing ' + res.name + ' content: ' + e);
+            console.error('ResourceDetailComponent --> 
setProviderConfigContent() --> Error parsing ' + res.name + ' content: ' + e);
           }
       }
   }
@@ -167,7 +161,248 @@ export class ResourceDetailComponent implements OnInit {
         }
         this.descriptor = tempDesc;
       } catch (e) {
-        console.error('Error parsing '+ res.name + ' content: ' + e);
+        console.error('ResourceDetailComponent.setDescriptorContent: Error 
parsing '+ res.name + ' content: ' + e);
+      }
+    }
+  }
+
+  persistChanges() {
+    switch(this.resourceType) {
+        case 'Provider Configurations' : {
+            this.persistProviderConfiguration();
+            break;
+        }
+        case 'Descriptors': {
+            this.persistDescriptor();
+        }
+    }
+    this.setResource(this.resource); // Refresh the detail presentation to 
reflect the saved state
+  }
+
+  persistProviderConfiguration() {
+    let content;
+    let ext = this.resource.name.split('.').pop();
+    switch(ext) {
+      case 'json': {
+        content = this.serializeProviderConfiguration(this.providers, 'json');
+        break;
+      }
+      case 'yaml':
+      case 'yml': {
+        content = this.serializeProviderConfiguration(this.providers, 'yaml');
+        break;
+      }
+      case 'xml': {
+        // We're not going to bother serializing XML. Rather, delete the 
original XML resource, and replace it
+        // with JSON
+        console.debug('Replacing XML provider configuration ' + 
this.resource.name + ' with JSON...');
+
+        // Generate the JSON representation of the updated provider 
configuration
+        content = this.serializeProviderConfiguration(this.providers, 'json');
+
+        let replacementResource = new Resource();
+        replacementResource.name = this.resource.name.slice(0, -4) + '.json';
+        replacementResource.href = this.resource.href;
+
+        // Delete the XML resource
+        this.resourceService.deleteResource(this.resource.href).then(() => {
+          // Save the updated content
+          this.resourceService.saveResource(replacementResource, 
content).then(() => {
+            // Refresh the presentation
+            this.changedProviders = null;
+            this.setResource(replacementResource);
+          });
+        });
+        break;
+      }
+    }
+
+    // For the non-XML provider configuration cases, simply save the changes
+    if (ext !== 'xml') {
+        // Save the updated content
+        this.resourceService.saveResource(this.resource, content).then(result 
=> {
+            // Refresh the presentation
+            this.changedProviders = null;
+            this.setResource(this.resource);
+        });
+    }
+  }
+
+
+  persistDescriptor() {
+    let content;
+    let ext = this.resource.name.split('.').pop();
+    switch(ext) {
+      case 'json': {
+        content = this.serializeDescriptor(this.descriptor, 'json');
+        break;
+      }
+      case 'yaml':
+      case 'yml': {
+        content = this.serializeDescriptor(this.descriptor, 'yaml');
+        break;
+      }
+    }
+    this.resourceService.saveResource(this.resource, content);
+
+    // Refresh the presentation
+    this.setResource(this.resource);
+  }
+
+
+  serializeDescriptor(desc: Descriptor, format: string): string {
+      let serialized: string;
+
+      let tmp = {};
+      tmp['discovery-address'] = desc.discoveryAddress;
+      tmp['discovery-user'] = desc.discoveryUser;
+      if (desc.discoveryPassAlias) {
+        tmp['discovery-pwd-alias'] = desc.discoveryPassAlias;
+      }
+      tmp['cluster'] = desc.discoveryCluster;
+      tmp['provider-config-ref'] = desc.providerConfig;
+      tmp['services'] = desc.services;
+
+      // Scrub any UI-specific service properties
+      for(let service of tmp['services']) {
+        if(service.hasOwnProperty('showParams')) {
+          delete service.showParams;
+        }
+        if(service.hasOwnProperty('showUrls')) {
+          delete service.showUrls;
+        }
+      }
+
+      switch(format) {
+          case 'json': {
+              serialized = JSON.stringify(tmp, null, 2);
+              break;
+          }
+          case 'yaml': {
+              let yaml = require('js-yaml');
+              serialized = '---\n' + yaml.dump(tmp);
+              break;
+          }
+      }
+
+      return serialized;
+  }
+
+
+  serializeProviderConfiguration(providers: Array<ProviderConfig>, format: 
string): string {
+    let serialized: string;
+
+    let tmp = {};
+    tmp['providers'] = providers;
+
+    // Scrub any UI-specific provider properties
+    for(let pc of tmp['providers']) {
+      if(pc.hasOwnProperty('show')) {
+        delete pc.show;
+      }
+      if(pc.hasOwnProperty('showParamDetails')) {
+        delete pc.showParamDetails;
+      }
+    }
+
+    switch(format) {
+        case 'json': {
+            serialized = JSON.stringify(tmp, null, 2);
+            break;
+        }
+        case 'yaml': {
+            let yaml = require('js-yaml');
+            serialized = '---\n' + yaml.dump(tmp);
+            break;
+        }
+    }
+
+    return serialized;
+  }
+
+
+  discardChanges() {
+    this.changedProviders = null;
+    this.setResource(this.resource); // Reset the resource to refresh to the 
original content
+  }
+
+
+  deleteResource() {
+    this.resourceService.deleteResource(this.resource.href);
+    this.resourceTypesService.selectResourceType(this.resourceType); // This 
refreshes the list of resources
+  }
+
+
+  onRemoveProvider(name: string) {
+    console.debug('ResourceDetailComponent --> onRemoveProvider() --> ' + 
name);
+    for(let i = 0; i < this.providers.length; i++) {
+      if(this.providers[i].name === name) {
+        this.providers.splice(i, 1);
+        break;
+      }
+    }
+    this.changedProviders = this.providers;
+  }
+
+
+  onRemoveProviderParam(pc: ProviderConfig, paramName: string) {
+    console.debug('ResourceDetailComponent --> onRemoveProviderParam() --> ' + 
pc.name + ' --> ' + paramName);
+    if(pc.params.hasOwnProperty(paramName)) {
+        delete pc.params[paramName];
+    }
+    this.changedProviders = this.providers;
+  }
+
+
+  onRemoveDescriptorService(serviceName: string) {
+    console.debug('ResourceDetailComponent --> onRemoveDescriptorService() --> 
' + serviceName);
+    for(let i = 0; i < this.descriptor.services.length; i++) {
+      if(this.descriptor.services[i].name === serviceName) {
+        this.descriptor.services.splice(i, 1);
+        this.descriptor.setDirty();
+        break;
+      }
+    }
+  }
+
+
+  onRemoveDescriptorServiceParam(serviceName: string, paramName: string) {
+    console.debug('ResourceDetailComponent --> 
onRemoveDescriptorServiceParam() --> ' + serviceName + ' : ' + paramName);
+    let done: boolean = false;
+    for(let i = 0; i < this.descriptor.services.length; i++) {
+      if(this.descriptor.services[i].name === serviceName) {
+        let service = this.descriptor.services[i];
+        if(service.params.hasOwnProperty(paramName)) {
+          delete service.params[paramName];
+          this.descriptor.setDirty();
+          done = true;
+          break;
+        }
+      }
+      if (done) { // Stop checking services if it has already been handled
+        break;
+      }
+    }
+  }
+
+
+  onRemoveDescriptorServiceURL(serviceName: string, serviceUrl: string) {
+    console.debug('ResourceDetailComponent --> 
onRemoveDescriptorServiceParam() --> ' + serviceName + ' : ' + serviceUrl);
+    let done: boolean = false;
+    for(let i = 0; i < this.descriptor.services.length; i++) {
+      if(this.descriptor.services[i].name === serviceName) {
+        let service = this.descriptor.services[i];
+        for(let j = 0; j < service.urls.length; j++) {
+          if(service.urls[j] === serviceUrl) {
+            service.urls.splice(j, 1);
+            this.descriptor.setDirty();
+            done = true;
+            break;
+          }
+        }
+      }
+      if (done) { // Stop checking services if it has already been handled
+        break;
       }
     }
   }
@@ -193,10 +428,12 @@ export class ResourceDetailComponent implements OnInit {
     return result;
   }
 
+
   hasSelectedResource(): boolean {
     return Boolean(this.resource) && Boolean(this.resource.name);
   }
 
+
   getTitleSubject(): string {
       switch(this.resourceType) {
           case 'Topologies': {

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/src/app/resource/resource.component.ts
----------------------------------------------------------------------
diff --git a/gateway-admin-ui/src/app/resource/resource.component.ts 
b/gateway-admin-ui/src/app/resource/resource.component.ts
index 264e03e..e96bbde 100644
--- a/gateway-admin-ui/src/app/resource/resource.component.ts
+++ b/gateway-admin-ui/src/app/resource/resource.component.ts
@@ -46,6 +46,11 @@ export class ResourceComponent implements OnInit {
 
 
   setResourceType(resType: string) {
+    //console.debug('ResourceComponent--> setResourceType --> ' + resType);
+
+    // Clear the selected resource, so it can be removed from the list on 
refresh if necessary
+    this.selectedResource = null;
+
     this.resourceType = resType;
     this.resourceService.selectedResourceType(this.resourceType);
     this.resources = []; // Clear the table before loading the new resources

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-admin-ui/src/app/resource/resource.service.ts
----------------------------------------------------------------------
diff --git a/gateway-admin-ui/src/app/resource/resource.service.ts 
b/gateway-admin-ui/src/app/resource/resource.service.ts
index d6c8327..fd698a0 100644
--- a/gateway-admin-ui/src/app/resource/resource.service.ts
+++ b/gateway-admin-ui/src/app/resource/resource.service.ts
@@ -19,6 +19,8 @@ import { HttpHeaders, HttpClient} from '@angular/common/http';
 import 'rxjs/add/operator/toPromise';
 import { Subject } from 'rxjs/Subject';
 import { Resource } from './resource';
+import {ProviderConfig} from "../resource-detail/provider-config";
+import {Descriptor} from "../resource-detail/descriptor";
 
 
 @Injectable()
@@ -38,6 +40,12 @@ export class ResourceService {
     changedResourceSource = new Subject<string>();
     changedResource$ = this.changedResourceSource.asObservable();
 
+    changedProviderConfigurationSource = new Subject<Array<ProviderConfig>>();
+    changedProviderConfiguration$ = 
this.changedProviderConfigurationSource.asObservable();
+
+    changedDescriptorSource = new Subject<Descriptor>();
+    changedDescriptor = this.changedDescriptorSource.asObservable();
+
     constructor(private http: HttpClient) { }
 
     getResources(resType: string): Promise<Resource[]> {
@@ -56,7 +64,7 @@ export class ResourceService {
 
     getProviderConfigResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
         return this.http.get(this.providersUrl, { headers: headers })
                         .toPromise()
                         .then(response => response['items'] as Resource[])
@@ -65,7 +73,7 @@ export class ResourceService {
 
     getDescriptorResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
         return this.http.get(this.descriptorsUrl, { headers: headers })
                         .toPromise()
                         .then(response => response['items'] as Resource[])
@@ -74,7 +82,7 @@ export class ResourceService {
 
     getTopologyResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
         return this.http.get(this.topologiesUrl, { headers: headers })
                         .toPromise()
                         .then(response => response['topologies'].topology as 
Resource[])
@@ -84,44 +92,46 @@ export class ResourceService {
     getResource(resType: string, res : Resource): Promise<string> {
         let headers = new HttpHeaders();
         headers = (resType === 'Topologies') ? this.addXmlHeaders(headers) : 
this.addHeaders(headers, res.name);
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
 
-        return this.http.get(res.href, { headers: headers, responseType: 
'text' })
-                        .toPromise()
-                        .then(response => {
-                            console.debug('ResourceService --> getResource() 
--> response: ' + response);
-                            return response;
-                        })
-                        .catch(this.handleError);
+        return this.http.get(res.href, {headers: headers, responseType: 
'text'})
+            .toPromise()
+            .then(response => {
+                console.debug('ResourceService --> Loading resource ' + 
res.name + ' :\n' + response);
+                return response;
+            })
+            .catch(this.handleError);
     }
 
-    saveResource(url: string, xml : string): Promise<string> {
-        let headers = this.addXmlHeaders(new HttpHeaders());
+    saveResource(resource: Resource, content: string): Promise<string> {
+        let headers = this.addHeaders(new HttpHeaders(), resource.name);
         headers = this.addCsrfHeaders(headers);
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
 
-        return this.http.put(url, xml, {headers: headers})
+        console.debug('ResourceService --> Persisting ' + resource.name + '\n' 
+ content);
+
+        return this.http.put(resource.href, content, {headers: headers})
                         .toPromise()
-                        .then(() => xml)
+                        .then(() => content)
                         .catch(this.handleError);
     }
 
-    createResource(name: string, xml : string): Promise<string> {
-        let headers = this.addXmlHeaders(new HttpHeaders());
+    createResource(resType: string, resource: Resource, content : string): 
Promise<string> {
+        let headers = this.addHeaders(new HttpHeaders(), resource.name);
         headers = this.addCsrfHeaders(headers);
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
 
-        let url = this.topologiesUrl + "/" + name;
-        return this.http.put(url, xml, {headers: headers})
+        let url = ((resType === 'Descriptors') ? this.descriptorsUrl : 
this.providersUrl) + '/' + name;
+        return this.http.put(url, content, {headers: headers})
                         .toPromise()
-                        .then(() => xml)
+                        .then(() => content)
                         .catch(this.handleError);
     }
 
     deleteResource(href: string): Promise<string> {
         let headers = this.addJsonHeaders(new HttpHeaders());
         headers = this.addCsrfHeaders(headers);
-        this.logHeaders(headers);
+        //this.logHeaders(headers);
 
         return this.http.delete(href, { headers: headers } )
                         .toPromise()
@@ -176,10 +186,17 @@ export class ResourceService {
         this.selectedResourceSource.next(value);
     }
 
-    changedResource(value: string) {
+    resourceChanged(value: string) {
         this.changedResourceSource.next(value);
     }
 
+    providerConfigurationChanged(pc: Array<ProviderConfig>) {
+        this.changedProviderConfigurationSource.next(pc);
+    }
+
+    descriptorChanged(desc: Descriptor) {
+        this.changedDescriptorSource.next(desc);
+    }
 
     public getResourceDisplayName(res: Resource): string {
         if (res.name) {
@@ -196,7 +213,7 @@ export class ResourceService {
     }
 
     private logHeaders(headers: HttpHeaders) {
-        let debugMsg = 'ResourceService --> Request header count: ' + 
headers.keys().length + '\n';
+        let debugMsg = 'ResourceService --> Request headers:\n';
         headers.keys().forEach(key => {
             debugMsg += '  ' + key + '=' + headers.get(key) + '\n';
         });

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-applications/src/main/resources/applications/admin-ui/app/index.html
----------------------------------------------------------------------
diff --git 
a/gateway-applications/src/main/resources/applications/admin-ui/app/index.html 
b/gateway-applications/src/main/resources/applications/admin-ui/app/index.html
index 37340a6..b18f391 100644
--- 
a/gateway-applications/src/main/resources/applications/admin-ui/app/index.html
+++ 
b/gateway-applications/src/main/resources/applications/admin-ui/app/index.html
@@ -14,4 +14,4 @@
 --><!doctype html><html><head><meta charset="utf-8"><title>Apache Knox 
Manager</title><meta name="viewport" 
content="width=device-width,initial-scale=1"><link rel="icon" 
type="image/x-icon" href="favicon.ico"><meta name="viewport" 
content="width=device-width,initial-scale=1"><!-- Latest compiled and minified 
CSS --><link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"; 
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
 crossorigin="anonymous"><!-- Optional theme --><link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css";
 
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
 crossorigin="anonymous"><!-- Custom styles for this template --><link 
href="assets/sticky-footer.css" rel="stylesheet"><script 
src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js";></script><!--
 Latest compiled and minified JavaScript --><sc
 ript src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"; 
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
 crossorigin="anonymous"></script><script 
src="assets/vkbeautify.js"></script><!--
   <script src="assets/esprima.js"></script>
   <script src="assets/js-yaml.min.js"></script>
-  --><link href="styles.2ee5b7f4cd59a6cf015e.bundle.css" 
rel="stylesheet"/></head><body><div class="navbar-wrapper"><div 
class="container-fluid"><nav class="navbar navbar-inverse 
navbar-static-top"><div class="container-fluid"><div 
class="navbar-header"><button type="button" class="navbar-toggle collapsed" 
data-toggle="collapse" data-target="#navbar" aria-expanded="false" 
aria-controls="navbar"><span class="sr-only">Toggle navigation</span> <span 
class="icon-bar"></span> <span class="icon-bar"></span> <span 
class="icon-bar"></span></button> <a class="navbar-brand" href="#"><img 
style="max-width:200px; margin-top: -9px;" 
src="assets/knox-logo-transparent.gif" alt="Apache Knox 
Manager"></a></div></div></nav></div><!-- Content 
--><resource-management></resource-management><footer class="footer"><div>Knox 
Manager Version 0.1.0</div><gateway-version></gateway-version></footer><script 
type="text/javascript" 
src="inline.1b8a794b441ffd968489.bundle.js"></script><script 
type="text/javascript
 " src="scripts.c50bb762c438ae0f8842.bundle.js"></script><script 
type="text/javascript" 
src="main.10e7c17d5707ac382dbb.bundle.js"></script></div></body></html>
\ No newline at end of file
+  --><link href="styles.2ee5b7f4cd59a6cf015e.bundle.css" 
rel="stylesheet"/></head><body><div class="navbar-wrapper"><div 
class="container-fluid"><nav class="navbar navbar-inverse 
navbar-static-top"><div class="container-fluid"><div 
class="navbar-header"><button type="button" class="navbar-toggle collapsed" 
data-toggle="collapse" data-target="#navbar" aria-expanded="false" 
aria-controls="navbar"><span class="sr-only">Toggle navigation</span> <span 
class="icon-bar"></span> <span class="icon-bar"></span> <span 
class="icon-bar"></span></button> <a class="navbar-brand" href="#"><img 
style="max-width:200px; margin-top: -9px;" 
src="assets/knox-logo-transparent.gif" alt="Apache Knox 
Manager"></a></div></div></nav></div><!-- Content 
--><resource-management></resource-management><footer class="footer"><div>Knox 
Manager Version 0.1.0</div><gateway-version></gateway-version></footer><script 
type="text/javascript" 
src="inline.1bf9c629c91002a65d30.bundle.js"></script><script 
type="text/javascript
 " src="scripts.c50bb762c438ae0f8842.bundle.js"></script><script 
type="text/javascript" 
src="main.4689931d0eaf6a8f51c9.bundle.js"></script></div></body></html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1b8a794b441ffd968489.bundle.js
----------------------------------------------------------------------
diff --git 
a/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1b8a794b441ffd968489.bundle.js
 
b/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1b8a794b441ffd968489.bundle.js
deleted file mode 100644
index d758f83..0000000
--- 
a/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1b8a794b441ffd968489.bundle.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e){var 
n=window.webpackJsonp;window.webpackJsonp=function(r,c,u){for(var 
a,i,f,l=0,s=[];l<r.length;l++)t[i=r[l]]&&s.push(t[i][0]),t[i]=0;for(a in 
c)Object.prototype.hasOwnProperty.call(c,a)&&(e[a]=c[a]);for(n&&n(r,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=o(o.s=u[l]);return
 f};var r={},t={2:0};function o(n){if(r[n])return r[n].exports;var 
t=r[n]={i:n,l:!1,exports:{}};return 
e[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.e=function(e){var 
n=t[e];if(0===n)return new Promise(function(e){e()});if(n)return n[2];var r=new 
Promise(function(r,o){n=t[e]=[r,o]});n[2]=r;var 
c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,o.nc&&u.setAttribute("nonce",o.nc),u.src=o.p+""+e+"."+{0:"10e7c17d5707ac382dbb",1:"aed76669724804835353"}[e]+".chunk.js";var
 a=setTimeout(i,12e4);function i(){u.onerror=u.onload=null,clearTimeout(a);var 
n=t[e];0!==n&&(n&&n[1](new Error("Loading chu
 nk "+e+" failed.")),t[e]=void 0)}return 
u.onerror=u.onload=i,c.appendChild(u),r},o.m=e,o.c=r,o.d=function(e,n,r){o.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},o.n=function(e){var
 n=e&&e.__esModule?function(){return e.default}:function(){return e};return 
o.d(n,"a",n),n},o.o=function(e,n){return 
Object.prototype.hasOwnProperty.call(e,n)},o.p="",o.oe=function(e){throw 
console.error(e),e}}([]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/cd336759/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1bf9c629c91002a65d30.bundle.js
----------------------------------------------------------------------
diff --git 
a/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1bf9c629c91002a65d30.bundle.js
 
b/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1bf9c629c91002a65d30.bundle.js
new file mode 100644
index 0000000..4fef45c
--- /dev/null
+++ 
b/gateway-applications/src/main/resources/applications/admin-ui/app/inline.1bf9c629c91002a65d30.bundle.js
@@ -0,0 +1 @@
+!function(e){var 
n=window.webpackJsonp;window.webpackJsonp=function(r,c,a){for(var 
u,i,f,l=0,s=[];l<r.length;l++)t[i=r[l]]&&s.push(t[i][0]),t[i]=0;for(u in 
c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(n&&n(r,c,a);s.length;)s.shift()();if(a)for(l=0;l<a.length;l++)f=o(o.s=a[l]);return
 f};var r={},t={2:0};function o(n){if(r[n])return r[n].exports;var 
t=r[n]={i:n,l:!1,exports:{}};return 
e[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.e=function(e){var 
n=t[e];if(0===n)return new Promise(function(e){e()});if(n)return n[2];var r=new 
Promise(function(r,o){n=t[e]=[r,o]});n[2]=r;var 
c=document.getElementsByTagName("head")[0],a=document.createElement("script");a.type="text/javascript",a.charset="utf-8",a.async=!0,a.timeout=12e4,o.nc&&a.setAttribute("nonce",o.nc),a.src=o.p+""+e+"."+{0:"4689931d0eaf6a8f51c9",1:"aed76669724804835353"}[e]+".chunk.js";var
 u=setTimeout(i,12e4);function i(){a.onerror=a.onload=null,clearTimeout(u);var 
n=t[e];0!==n&&(n&&n[1](new Error("Loading chu
 nk "+e+" failed.")),t[e]=void 0)}return 
a.onerror=a.onload=i,c.appendChild(a),r},o.m=e,o.c=r,o.d=function(e,n,r){o.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},o.n=function(e){var
 n=e&&e.__esModule?function(){return e.default}:function(){return e};return 
o.d(n,"a",n),n},o.o=function(e,n){return 
Object.prototype.hasOwnProperty.call(e,n)},o.p="",o.oe=function(e){throw 
console.error(e),e}}([]);
\ No newline at end of file

Reply via email to