RIFT-14134, RIFT-14548: RO CRU and Instantiate Data Centers 10/310/1
authorLaurence Maultsby <laurence.maultsby@riftio.com>
Mon, 12 Sep 2016 16:14:43 +0000 (12:14 -0400)
committerLaurence Maultsby <laurence.maultsby@riftio.com>
Mon, 12 Sep 2016 16:32:31 +0000 (12:32 -0400)
Signed-off-by: Laurence Maultsby <laurence.maultsby@riftio.com>
43 files changed:
skyquake/framework/widgets/panel/panel.jsx
skyquake/framework/widgets/panel/panel.scss
skyquake/framework/widgets/skyquake_container/skyquakeContainer.jsx
skyquake/framework/widgets/skyquake_container/skyquakeContainerSource.js
skyquake/framework/widgets/skyquake_container/skyquakeContainerStore.js
skyquake/plugins/accounts/routes.js
skyquake/plugins/accounts/src/account/accountActions.js
skyquake/plugins/accounts/src/account/accountSource.js
skyquake/plugins/accounts/src/account/accountStore.js
skyquake/plugins/accounts/src/account/accountsDashboard.jsx
skyquake/plugins/accounts/src/account_sidebar/accountSidebar.jsx
skyquake/plugins/config/CMakeLists.txt [new file with mode: 0644]
skyquake/plugins/config/api/ro.js [new file with mode: 0644]
skyquake/plugins/config/config.json [new file with mode: 0644]
skyquake/plugins/config/images/OpenDaylight_logo.png [new file with mode: 0644]
skyquake/plugins/config/images/aws.png [new file with mode: 0644]
skyquake/plugins/config/images/juju.svg [new file with mode: 0644]
skyquake/plugins/config/images/openmano.png [new file with mode: 0644]
skyquake/plugins/config/images/openstack-horizontal.png [new file with mode: 0644]
skyquake/plugins/config/images/openstack.png [new file with mode: 0644]
skyquake/plugins/config/images/riftio.png [new file with mode: 0644]
skyquake/plugins/config/package.json [new file with mode: 0644]
skyquake/plugins/config/routes.js [new file with mode: 0644]
skyquake/plugins/config/scripts/build.sh [new file with mode: 0755]
skyquake/plugins/config/scripts/install.sh [new file with mode: 0755]
skyquake/plugins/config/server.js [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/config.scss [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/configActions.js [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/configSource.js [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/configStore.js [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/dashboard.jsx [new file with mode: 0644]
skyquake/plugins/config/src/dashboard/inputs.jsx [new file with mode: 0644]
skyquake/plugins/config/src/main.js [new file with mode: 0644]
skyquake/plugins/config/webpack.production.config.js [new file with mode: 0644]
skyquake/plugins/launchpad/api/launchpad.js
skyquake/plugins/launchpad/src/instantiate/instantiateDashboard.jsx
skyquake/plugins/launchpad/src/instantiate/instantiateInputParams.jsx
skyquake/plugins/launchpad/src/instantiate/instantiateParameters.jsx
skyquake/plugins/launchpad/src/instantiate/instantiateStore.js
skyquake/plugins/launchpad/src/instantiate/launchNetworkServiceActions.js
skyquake/plugins/launchpad/src/instantiate/launchNetworkServiceSource.js
skyquake/plugins/launchpad/src/launchpadFleetActions.js
skyquake/plugins/launchpad/src/launchpadFleetSource.js

index e8a3118..4ef7c89 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,20 +25,21 @@ export class Panel extends Component {
     render() {
         let self = this;
         let {children, className, title, ...props} = self.props;
-        let classRoot = className ? ' ' + className : ' '
+        let classRoot = className ? ' ' + className : ' ';
+        let hasCorners = this.props['no-corners'];
         let titleTag = title ? <header className="skyquakePanel-title">{title}</header> : '';
         return (
             <section className={'skyquakePanel' + classRoot} style={props.style}>
-                <i className="corner-accent top left"></i>
-                <i className="corner-accent top right"></i>
+                { !hasCorners ? <i className="corner-accent top left"></i> : null }
+                { !hasCorners ? <i className="corner-accent top right"></i> : null }
                 {titleTag}
                 <div className="skyquakePanel-wrapper">
                     <div className={(classRoot ? 'skyquakePanel-body ' + decorateClassNames(classRoot, '-body') : 'skyquakePanel-body')}>
                             {children}
                     </div>
                 </div>
-                <i className="corner-accent bottom left"></i>
-                <i className="corner-accent bottom right"></i>
+                { !hasCorners ? <i className="corner-accent bottom left"></i> : null }
+                { !hasCorners ? <i className="corner-accent bottom right"></i> : null }
             </section>
         )
     }
@@ -50,7 +51,8 @@ Panel.defaultProps = {
 
 export class PanelWrapper extends Component {
     render() {
-        return (<div className={'skyquakePanelWrapper'}>
+        return (
+        <div className={'skyquakePanelWrapper ' + this.props.className} style={this.props.style}>
             {this.props.children}
         </div>)
     }
@@ -60,7 +62,7 @@ export default Panel;
 
 
 function decorateClassNames(className, addendum) {
-    return className.split(' ').map(function(c) {
+    return className.trim().split(' ').map(function(c) {
         return c + addendum
     }).join(' ');
 }
index ff36bbf..2a31b1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
         }
 }
 
+.skyquakePanelWrapper.column {
+    .skyquakePanel-wrapper {
+        height:auto;
+    }
+}
+
 /* Style for storybook */
 body{
     height:100%;
index eca9413..53a382f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -83,7 +83,7 @@ export default class skyquakeContainer extends React.Component {
     }
 
     render() {
-        const {displayNotification, notificationMessage, displayScreenLoader, ...state} = this.state;
+        const {displayNotification, notificationMessage, displayScreenLoader, notificationType, ...state} = this.state;
         var html;
 
         if (this.matchesLoginUrl()) {
@@ -104,7 +104,7 @@ export default class skyquakeContainer extends React.Component {
                         <Crouton
                             id={Date.now()}
                             message={notificationMessage}
-                            type={"error"}
+                            type={notificationType}
                             hidden={!(displayNotification && notificationMessage)}
                             onDismiss={SkyquakeContainerActions.hideNotification}
                         />
index fe4a7b0..aa4e744 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -35,6 +35,7 @@ class SkyquakeContainerStore {
         //Notification defaults
         this.notificationMessage = '';
         this.displayNotification = false;
+        this.notificationType = 'error';
         //Screen Loader default
         this.displayScreenLoader = false;
         this.bindActions(SkyquakeContainerActions);
@@ -81,9 +82,9 @@ class SkyquakeContainerStore {
         let connection = data.connection;
         let streamSource = data.streamSource;
         console.log('Success opening notification socket for stream ', streamSource);
-        
+
         let ws = window.multiplexer.channel(connection);
-        
+
         if (!connection) return;
         self.setState({
             socket: ws.ws,
@@ -162,20 +163,21 @@ class SkyquakeContainerStore {
 
     //Notifications
     showNotification = (data) => {
-        if(typeof(data) == 'string') {
-            this.setState({
+        let state = {
                 displayNotification: true,
-                notificationMessage: data
-            });
+                notificationMessage: data,
+                notificationType: 'error',
+                displayScreenLoader: false
+            }
+        if(typeof(data) == 'string') {
+
         } else {
-            if(data.type == 'error') {
-                this.setState({
-                    displayNotification: true,
-                    notificationMessage: data.msg,
-                    displayScreenLoader: false
-                });
+            state.notificationMessage = data.msg;
+            if(data.type == 'success') {
+                state.notificationType = 'success';
             }
         }
+        this.setState(state);
     }
     hideNotification = () => {
         this.setState({
index 46eb736..ab831e5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -70,6 +70,6 @@ var accountsAPI = require('./api/accounts.js')
             utils.sendErrorResponse(error, res);
         });
     })
-
+    utils.passThroughConstructor(app);
 
 module.exports = app;
index d5111f4..15c48f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,6 +31,7 @@ module.exports = function(Alt) {
                                        'deleteAccountSuccess',
                                        'deleteAccountLoading',
                                        'deleteAccountFail',
-                                       'viewAccount'
+                                       'viewAccount',
+                                       'getResourceOrchestratorSuccess'
                                        );
 }
index 5fa7869..c2aa4d4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -212,7 +212,36 @@ module.exports = function(Alt) {
           success: Alt.actions.global.deleteAccountSuccess,
           loading: Alt.actions.global.deleteAccountLoading,
           error: Alt.actions.global.showNotification
-      }
+      },
+        getResourceOrchestrator: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  contentType: "application/json",
+                  success: function(data) {
+                    resolve(data["rw-launchpad:resource-orchestrator"]);
+                  },
+                  error: function(error) {
+                    console.log("There was an error updating the account: ", arguments);
+
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  return reject('error');
+                });
+              });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error retrieving the resource orchestrator information.'
+          }),
+          success: Alt.actions.global.getResourceOrchestratorSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
     }
 }
 
index f6e2ef1..002a9e1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -126,16 +126,6 @@ let AccountMeta = {
                 ref: 'floating-ip-pool',
                 optional: true
             }],
-            "openmano": [{
-                label: "Host",
-                ref: 'host'
-            }, {
-                label: "Port",
-                ref: 'port'
-            }, {
-                label: "Tenant ID",
-                ref: 'tenant-id'
-            }],
             "openvim": [{
                 label: "Host",
                 ref: 'host'
@@ -174,9 +164,6 @@ let AccountMeta = {
         }, {
             "name": "Cloudsim",
             "account-type": "cloudsim_proxy"
-        }, {
-            "name": "Open Mano",
-            "account-type": "openmano"
         }, {
             "name": "AWS",
             "account-type": "aws"
@@ -189,7 +176,6 @@ let AccountMeta = {
     },
     image: {
         "aws": require("../../images/aws.png"),
-        "openmano": altImage || require("../../images/openmano.png"),
         "openvim": require("../../images/openmano.png"),
         "openstack": require("../../images/openstack.png"),
         "cloudsim_proxy": require("../../images/riftio.png"),
@@ -199,7 +185,6 @@ let AccountMeta = {
     },
     labelByType: {
         "aws": "AWS",
-        "openmano": "OpenStack",
         "openvim": "Open VIM",
         "openstack": "OpenStack",
         "cloudsim_proxy": "Cloudsim"
@@ -216,6 +201,7 @@ export default class AccountStore {
         this.refreshingAll = false;
         this.sdnOptions = [];
         this.AccountMeta = AccountMeta;
+        this.showVIM = true;
         this.bindActions(AccountActions(this.alt));
         this.registerAsync(AccountSource);
         this.exportPublicMethods({
@@ -248,6 +234,14 @@ export default class AccountStore {
     }
     refreshCloudAccountSuccess = () => {
 
+    }
+    getResourceOrchestratorSuccess = (data) => {
+        this.alt.actions.global.hideScreenLoader.defer();
+        if(data['account-type'] == 'openmano') {
+            this.setState({
+                showVIM: false
+            })
+        }
     }
     deleteAccountSuccess = (response) => {
         this.setState({
index c0e0680..f139b47 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,6 +32,7 @@ class AccountsDashboard extends React.Component {
     componentWillMount() {
         this.Store.listen(this.updateState);
         this.Store.openAccountsSocket();
+        this.Store.getResourceOrchestrator();
     }
     componentWillUnmount() {
         this.Store.closeSocket();
index ee86a7b..6276194 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -123,18 +123,22 @@ class AccountSidebar extends React.Component{
         html = (
             <div className='accountSidebar'>
                  <Button className="refreshList light" onClick={this.props.store.refreshAll.bind(this, AccountData)} label={this.props.refreshingAll ? 'Checking Connectivity Status...' : refreshStatus}></Button>
-                <h1>VIM Accounts</h1>
-                {cloudAccounts}
-                <DashboardCard className="accountSidebarCard">
-                        <Link
-                        to={{pathname: '/accounts/cloud/create'}}
-                        title="Create Cloud Account"
-                        className={'accountSidebarCard_create'}
-                    >
-                            Add VIM Account
-                            <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
-                        </Link>
-                </DashboardCard>
+                {props.showVIM ? (
+                    <div>
+                        <h1>VIM Accounts</h1>
+                        {cloudAccounts}
+                        <DashboardCard className="accountSidebarCard">
+                                <Link
+                                to={{pathname: '/accounts/cloud/create'}}
+                                title="Create Cloud Account"
+                                className={'accountSidebarCard_create'}
+                            >
+                                    Add VIM Account
+                                    <img src={require("style/img/launchpad-add-fleet-icon.png")}/>
+                                </Link>
+                        </DashboardCard>
+                    </div>)
+                : null}
                 <h1>SDN Accounts</h1>
                 {sdnAccounts}
                 <DashboardCard className="accountSidebarCard">
diff --git a/skyquake/plugins/config/CMakeLists.txt b/skyquake/plugins/config/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9e1fe30
--- /dev/null
@@ -0,0 +1,37 @@
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(BEGIN)
+# Author(s): Kiran Kashalkar
+# Creation Date: 08/18/2015
+# RIFT_IO_STANDARD_CMAKE_COPYRIGHT_HEADER(END)
+
+##
+# DEPENDENCY ALERT
+# The submodule dependencies must be specified in the
+# .gitmodules.dep file at the top level (supermodule) directory
+# If this submodule depends other submodules remember to update
+# the .gitmodules.dep
+##
+
+cmake_minimum_required(VERSION 2.8)
+
+##
+# Submodule specific includes will go here,
+# These are specified here, since these variables are accessed
+# from multiple sub directories. If the variable is subdirectory
+# specific it must be declared in the subdirectory.
+##
+
+rift_externalproject_add(
+  config
+  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
+  CONFIGURE_COMMAND echo
+  BUILD_COMMAND
+    ${CMAKE_CURRENT_BINARY_DIR}/config/config-build/scripts/build.sh
+  INSTALL_COMMAND
+    ${CMAKE_CURRENT_SOURCE_DIR}/scripts/install.sh
+    ${CMAKE_CURRENT_BINARY_DIR}/config/config-build
+    ${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+    ${RIFT_SUBMODULE_INSTALL_PREFIX}/skyquake/${CMAKE_INSTALL_PREFIX}/usr/share/rw.ui/skyquake
+
+  BCACHE_COMMAND echo
+)
+
diff --git a/skyquake/plugins/config/api/ro.js b/skyquake/plugins/config/api/ro.js
new file mode 100644 (file)
index 0000000..99fb1d7
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var request = require('request');
+var Promise = require('bluebird');
+var rp = require('request-promise');
+var utils = require('../../../framework/core/api_utils/utils.js');
+var constants = require('../../../framework/core/api_utils/constants.js');
+var _ = require('underscore');
+var RO = {}
+RO.get = function(req) {
+return new Promise(function(resolve, reject) {
+        var self = this;
+        var api_server = req.query["api_server"];
+        var requestHeaders = {};
+        var url = utils.confdPort(api_server) + '/api/running/resource-orchestrator';
+        _.extend(
+            requestHeaders,
+            id ? constants.HTTP_HEADERS.accept.data : constants.HTTP_HEADERS.accept.collection, {
+                'Authorization': req.get('Authorization')
+            }
+        );
+
+        request({
+                url: url + '?deep',
+                type: 'GET',
+                headers: requestHeaders,
+                forever: constants.FOREVER_ON,
+                rejectUnauthorized: false
+            },
+            function(error, response, body) {
+                var data;
+                if (utils.validateResponse('RO.get', error, response, body, resolve, reject)) {
+                    try {
+                        data = JSON.parse(response.body);
+                        if (!id) {
+                            data = data.collection;
+                        }
+
+                        data = data[objKey]
+                    } catch (e) {
+                        console.log('Problem with "' + type.toUpperCase() + '.get"', e);
+                        var err = {};
+                        err.statusCode = 500;
+                        err.errorMessage = {
+                            error: 'Problem with "' + type.toUpperCase() + '.get": ' + e
+                        }
+                        return reject(err);
+                    }
+                    return resolve({
+                        statusCode: response.statusCode,
+                        data: data
+                    });
+                };
+            });
+    });
+}
+
+RO.update = updateAccount;
+
+function updateAccount(req) {
+    var self = this;
+    var api_server = req.query["api_server"];
+    var data = req.body;
+    var requestHeaders = {};
+    var url = utils.confdPort(api_server) + '/api/config/resource-orchestrator';
+    var method = 'PUT'
+
+    return new Promise(function(resolve, reject) {
+        _.extend(requestHeaders,
+            constants.HTTP_HEADERS.accept.data,
+            constants.HTTP_HEADERS.content_type.data, {
+                'Authorization': req.get('Authorization')
+            });
+        request({
+            url: url,
+            method: method,
+            headers: requestHeaders,
+            forever: constants.FOREVER_ON,
+            rejectUnauthorized: false,
+            json: data,
+        }, function(error, response, body) {
+            if (utils.validateResponse('RO.update', error, response, body, resolve, reject)) {
+                return resolve({
+                    statusCode: response.statusCode,
+                    data: JSON.stringify(response.body)
+                });
+            };
+        });
+    })
+}
+
+module.exports = RO;
diff --git a/skyquake/plugins/config/config.json b/skyquake/plugins/config/config.json
new file mode 100644 (file)
index 0000000..6fd9f43
--- /dev/null
@@ -0,0 +1,15 @@
+{
+    "root": "public",
+    "name": "Config",
+    "dashboard": "./dashboard/dashboard.jsx",
+    "order": 1,
+    "priority":1,
+    "routes": [
+    {
+        "label": "Configuration Dashboard",
+        "route": "accounts",
+        "component": "./dashboard/dashboard.jsx",
+        "path": "accounts",
+        "type": "internal"
+    }]
+}
diff --git a/skyquake/plugins/config/images/OpenDaylight_logo.png b/skyquake/plugins/config/images/OpenDaylight_logo.png
new file mode 100644 (file)
index 0000000..b9064ec
Binary files /dev/null and b/skyquake/plugins/config/images/OpenDaylight_logo.png differ
diff --git a/skyquake/plugins/config/images/aws.png b/skyquake/plugins/config/images/aws.png
new file mode 100644 (file)
index 0000000..ca57e1c
Binary files /dev/null and b/skyquake/plugins/config/images/aws.png differ
diff --git a/skyquake/plugins/config/images/juju.svg b/skyquake/plugins/config/images/juju.svg
new file mode 100644 (file)
index 0000000..142f608
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        width="1190.55px" height="841.89px" viewBox="0 0 1190.55 841.89" enable-background="new 0 0 1190.55 841.89"
+        xml:space="preserve">
+<g>
+       <g>
+               <path fill="#DD4814" d="M657.621,123.565c-73.488,0-133.063,59.573-133.063,133.062s59.576,133.069,133.063,133.069
+                       c73.49,0,133.064-59.58,133.064-133.069S731.111,123.565,657.621,123.565z"/>
+               <path fill="#FFFFFF" d="M593.043,338.909c-1.128,1.128-2.632,1.75-4.233,1.75l0,0c-1.605,0-3.112-0.624-4.244-1.757
+                       c-1.129-1.128-1.751-2.632-1.751-4.234c0-1.602,0.622-3.105,1.751-4.235c1.133-1.132,2.64-1.756,4.243-1.756
+                       c1.601,0,3.104,0.622,4.233,1.751c1.131,1.131,1.753,2.637,1.753,4.24C594.797,336.273,594.174,337.779,593.043,338.909z"/>
+               <path fill="#FFFFFF" d="M619.154,249.424c-0.603-1.92-1.47-3.498-2.579-4.69c-1.105-1.193-2.443-2.085-3.978-2.653
+                       c-1.543-0.574-3.291-0.865-5.194-0.865s-3.636,0.29-5.147,0.863c-1.509,0.569-2.834,1.462-3.939,2.654
+                       c-1.107,1.19-1.974,2.769-2.578,4.691c-0.606,1.933-0.914,4.299-0.914,7.032v69.735h-12.038V255.39
+                       c0-3.572,0.484-6.896,1.438-9.88c0.957-2.996,2.463-5.641,4.475-7.863c2.015-2.226,4.586-3.976,7.643-5.203
+                       c3.05-1.226,6.742-1.848,10.973-1.848c4.229,0,7.949,0.622,11.059,1.847c3.115,1.227,5.717,2.978,7.732,5.204
+                       c2.01,2.219,3.532,4.863,4.52,7.861c0.985,2.985,1.485,6.311,1.485,9.882V265h-12.038v-8.544
+                       C620.072,253.733,619.763,251.367,619.154,249.424z"/>
+               <path fill="#FFFFFF" d="M669.396,298.309c0,3.579-0.499,6.905-1.483,9.885c-0.985,2.99-2.507,5.634-4.521,7.86
+                       c-2.016,2.225-4.617,3.976-7.732,5.203c-3.112,1.226-6.833,1.848-11.06,1.848s-7.917-0.622-10.972-1.849
+                       c-3.055-1.226-5.626-2.976-7.642-5.201c-2.014-2.226-3.52-4.872-4.476-7.864c-0.954-2.983-1.437-6.308-1.437-9.882v-30.425h12.037
+                       v29.361c0,2.734,0.309,5.099,0.914,7.029c0.603,1.924,1.471,3.502,2.578,4.694c1.106,1.194,2.434,2.087,3.938,2.654
+                       c1.511,0.573,3.242,0.863,5.147,0.863c1.903,0,3.651-0.291,5.193-0.864c1.533-0.568,2.871-1.461,3.979-2.653
+                       c1.108-1.194,1.979-2.773,2.579-4.693c0.607-1.94,0.917-4.305,0.917-7.03v-29.361h12.038V298.309z"/>
+               <path fill="#FFFFFF" d="M657.387,259.099c0-3.302,2.685-5.989,5.986-5.989c3.305,0,5.995,2.687,5.995,5.989
+                       c0,3.305-2.69,5.994-5.995,5.994C660.071,265.093,657.387,262.404,657.387,259.099z"/>
+               <path fill="#FFFFFF" d="M693.726,174.163c-0.603-1.92-1.472-3.499-2.581-4.693c-1.107-1.193-2.446-2.085-3.979-2.652
+                       c-1.54-0.573-3.288-0.863-5.193-0.863c-1.903,0-3.635,0.29-5.146,0.862c-1.508,0.569-2.834,1.462-3.941,2.653
+                       c-1.105,1.193-1.973,2.772-2.575,4.693c-0.606,1.932-0.914,4.298-0.914,7.032v69.734h-12.038v-70.8
+                       c0-3.571,0.482-6.896,1.438-9.881c0.958-2.996,2.463-5.642,4.477-7.863c2.012-2.224,4.584-3.975,7.643-5.202
+                       c3.055-1.227,6.744-1.849,10.969-1.849c4.227,0,7.948,0.622,11.06,1.848c3.119,1.228,5.719,2.979,7.731,5.203
+                       c2.014,2.22,3.535,4.865,4.522,7.86c0.983,2.988,1.483,6.314,1.483,9.885v9.609h-12.04v-8.543
+                       C694.64,178.467,694.332,176.101,693.726,174.163z"/>
+               <path fill="#FFFFFF" d="M743.963,223.048c0,3.577-0.499,6.903-1.482,9.884c-0.986,2.993-2.506,5.638-4.518,7.86
+                       c-2.018,2.226-4.619,3.976-7.732,5.203c-3.115,1.226-6.837,1.848-11.06,1.848c-4.229,0-7.921-0.622-10.974-1.849
+                       c-3.057-1.226-5.626-2.976-7.642-5.201c-2.012-2.223-3.518-4.868-4.476-7.863c-0.954-2.989-1.438-6.313-1.438-9.881v-30.426
+                       h12.038v29.361c0,2.729,0.308,5.095,0.915,7.031c0.603,1.92,1.469,3.499,2.58,4.692c1.106,1.194,2.432,2.087,3.937,2.654
+                       c1.513,0.573,3.243,0.863,5.146,0.863c1.907,0,3.654-0.291,5.194-0.864c1.534-0.566,2.872-1.459,3.981-2.653
+                       c1.106-1.19,1.975-2.769,2.577-4.692c0.607-1.936,0.916-4.301,0.916-7.031v-29.361h12.037V223.048z"/>
+       </g>
+       <g>
+               <path d="M360.49,646.299c-2.566,0-5.878-0.32-9.943-0.962c-4.06-0.642-7.482-1.498-10.264-2.565l3.849-24.377
+                       c2.14,0.641,4.601,1.172,7.377,1.603c2.781,0.427,5.347,0.642,7.698,0.642c10.265,0,17.586-3.157,21.972-9.461
+                       c4.38-6.311,6.575-15.557,6.575-27.746V419.527h29.83v163.584c0,21.38-4.866,37.257-14.594,47.631
+                       C393.257,641.112,379.094,646.299,360.49,646.299z"/>
+               <path d="M595.598,581.507c-6.847,1.714-15.877,3.528-27.103,5.454c-11.227,1.924-24.217,2.887-38.972,2.887
+                       c-12.83,0-23.63-1.875-32.396-5.614c-8.771-3.738-15.826-9.03-21.168-15.876c-5.348-6.842-9.197-14.916-11.547-24.218
+                       c-2.356-9.302-3.528-19.616-3.528-30.952v-93.66h29.83v87.245c0,20.317,3.208,34.856,9.622,43.622
+                       c6.416,8.771,17.21,13.15,32.396,13.15c3.208,0,6.52-0.105,9.943-0.32c3.417-0.211,6.625-0.481,9.623-0.802
+                       c2.992-0.321,5.718-0.642,8.179-0.963c2.457-0.32,4.22-0.691,5.293-1.122V419.527h29.83V581.507z"/>
+               <path d="M615.162,646.299c-2.566,0-5.879-0.32-9.943-0.962c-4.06-0.642-7.484-1.498-10.265-2.565l3.85-24.377
+                       c2.14,0.641,4.601,1.172,7.377,1.603c2.782,0.427,5.348,0.642,7.698,0.642c10.265,0,17.587-3.157,21.972-9.461
+                       c4.38-6.311,6.575-15.557,6.575-27.746V419.527h29.83v163.584c0,21.38-4.866,37.257-14.595,47.631
+                       C647.929,641.112,633.766,646.299,615.162,646.299z"/>
+               <path d="M850.27,581.507c-6.846,1.714-15.878,3.528-27.104,5.454c-11.227,1.924-24.217,2.887-38.972,2.887
+                       c-12.83,0-23.63-1.875-32.396-5.614c-8.771-3.738-15.827-9.03-21.17-15.876c-5.347-6.842-9.196-14.916-11.547-24.218
+                       c-2.355-9.302-3.528-19.616-3.528-30.952v-93.66h29.83v87.245c0,20.317,3.207,34.856,9.622,43.622
+                       c6.415,8.771,17.211,13.15,32.396,13.15c3.207,0,6.521-0.105,9.942-0.32c3.419-0.211,6.627-0.481,9.624-0.802
+                       c2.991-0.321,5.718-0.642,8.179-0.963c2.455-0.32,4.22-0.691,5.292-1.122V419.527h29.83V581.507z"/>
+       </g>
+</g>
+</svg>
diff --git a/skyquake/plugins/config/images/openmano.png b/skyquake/plugins/config/images/openmano.png
new file mode 100644 (file)
index 0000000..0557c00
Binary files /dev/null and b/skyquake/plugins/config/images/openmano.png differ
diff --git a/skyquake/plugins/config/images/openstack-horizontal.png b/skyquake/plugins/config/images/openstack-horizontal.png
new file mode 100644 (file)
index 0000000..e1426e5
Binary files /dev/null and b/skyquake/plugins/config/images/openstack-horizontal.png differ
diff --git a/skyquake/plugins/config/images/openstack.png b/skyquake/plugins/config/images/openstack.png
new file mode 100644 (file)
index 0000000..9ee6d77
Binary files /dev/null and b/skyquake/plugins/config/images/openstack.png differ
diff --git a/skyquake/plugins/config/images/riftio.png b/skyquake/plugins/config/images/riftio.png
new file mode 100644 (file)
index 0000000..19bdf36
Binary files /dev/null and b/skyquake/plugins/config/images/riftio.png differ
diff --git a/skyquake/plugins/config/package.json b/skyquake/plugins/config/package.json
new file mode 100644 (file)
index 0000000..b4de560
--- /dev/null
@@ -0,0 +1,54 @@
+{
+  "name": "config",
+  "version": "1.0.0",
+  "description": "",
+  "main": "routes.js",
+  "scripts": {
+    "start": "rm -rf public/ && mkdir public && cd public && mkdir build && cp ../src/index.html ./ && node ../server.js"
+  },
+  "author": "RIFT.io",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "alt": "^0.18.3",
+    "bluebird": "^3.4.1",
+    "express": "^4.13.3",
+    "history": "^1.17.0",
+    "jquery": "^2.2.1",
+    "json-loader": "^0.5.4",
+    "lodash": "^4.10.0",
+    "normalizr": "^2.1.0",
+    "open-iconic": "^1.1.1",
+    "prismjs": "^1.4.1",
+    "react": "^0.14.8",
+    "react-breadcrumbs": "^1.3.9",
+    "react-crouton": "^0.2.7",
+    "react-dom": "^0.14.6",
+    "react-router": "^2.0.1",
+    "react-slick": "^0.11.1",
+    "react-tabs": "^0.5.3",
+    "react-treeview": "^0.4.2",
+    "request-promise": "^3.0.0",
+    "underscore": "^1.8.3"
+  },
+  "devDependencies": {
+    "babel-core": "^6.4.5",
+    "babel-loader": "^6.2.1",
+    "babel-polyfill": "^6.9.1",
+    "babel-preset-es2015": "^6.6.0",
+    "babel-preset-react": "^6.5.0",
+    "babel-preset-stage-0": "^6.3.13",
+    "babel-runtime": "^6.3.19",
+    "cors": "^2.7.1",
+    "css-loader": "^0.23.1",
+    "file-loader": "^0.8.5",
+    "html-webpack-plugin": "^2.9.0",
+    "http-proxy": "^1.12.0",
+    "loaders.css": "^0.1.2",
+    "node-sass": "^3.4.2",
+    "react-addons-css-transition-group": "^0.14.7",
+    "sass-loader": "^3.1.2",
+    "style-loader": "^0.13.0",
+    "webpack": "^1.3.0",
+    "webpack-dev-server": "^1.10.1"
+  }
+}
diff --git a/skyquake/plugins/config/routes.js b/skyquake/plugins/config/routes.js
new file mode 100644 (file)
index 0000000..bf5915d
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var app = require('express').Router();
+var cors = require('cors');
+var utils = require('../../framework/core/api_utils/utils.js')
+var ro = require('./api/ro.js')
+ // Begin Accounts API
+     app.get('/resource-orchestrator', cors(), function(req, res) {
+        ro.get(req).then(function(data) {
+            utils.sendSuccessResponse(data, res);
+        }, function(error) {
+            utils.sendErrorResponse(error, res);
+        });
+    });
+    app.put('/resource-orchestrator', cors(), function(req, res) {
+        ro.update(req).then(function(data) {
+            utils.sendSuccessResponse(data, res);
+        }, function(error) {
+            utils.sendErrorResponse(error, res);
+        });
+    })
+
+    utils.passThroughConstructor(app);
+
+module.exports = app;
diff --git a/skyquake/plugins/config/scripts/build.sh b/skyquake/plugins/config/scripts/build.sh
new file mode 100755 (executable)
index 0000000..1758212
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+PLUGIN_NAME=accounts
+# change to the directory of this script
+THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+cd $THIS_DIR
+cd ..
+
+echo 'Building plugin '$PLUGIN_NAME
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME
+npm install
+echo 'Fetching third-party node_modules for '$PLUGIN_NAME'...done'
+echo 'Packaging '$PLUGIN_NAME' using webpack'
+ui_plugin_cmake_build=true ./node_modules/.bin/webpack --progress --config webpack.production.config.js
+echo 'Packaging '$PLUGIN_NAME' using webpack... done'
+echo 'Building plugin '$PLUGIN_NAME'... done'
diff --git a/skyquake/plugins/config/scripts/install.sh b/skyquake/plugins/config/scripts/install.sh
new file mode 100755 (executable)
index 0000000..9e6c59e
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# STANDARD_RIFT_IO_COPYRIGHT
+
+plugin=config
+source_dir=$1
+dest_dir=$2
+bcache_dir=$3
+
+# Create destination and build cache directories
+mkdir -p $dest_dir
+mkdir -p $bcache_dir
+
+# Create necessary directories under dest and cache dirs
+mkdir -p $dest_dir/framework
+mkdir -p $dest_dir/plugins
+mkdir -p $bcache_dir/framework
+mkdir -p $bcache_dir/plugins
+
+# Copy over built plugin's public folder, config.json, routes.js and api folder as per installed_plugins.txt
+mkdir -p $dest_dir/plugins/$plugin
+cp -Lrf $source_dir/public $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $dest_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $dest_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $dest_dir/plugins/$plugin/.
+tar -cf $dest_dir/plugins/$plugin/node_modules.tar $source_dir/node_modules $source_dir/package.json
+#cp -Lrp $source_dir/node_modules $dest_dir/plugins/$plugin/.
+mkdir -p $bcache_dir/plugins/$plugin
+cp -Lrf $source_dir/public $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/config.json $bcache_dir/plugins/$plugin/.
+cp -Lrf $source_dir/routes.js $bcache_dir/plugins/$plugin/.
+cp -Lrp $source_dir/api $bcache_dir/plugins/$plugin/.
+tar -cf $bcache_dir/plugins/$plugin/node_modules.tar $source_dir/node_modules $source_dir/package.json
+#cp -Lrp $source_dir/node_modules $bcache_dir/plugins/$plugin/.
diff --git a/skyquake/plugins/config/server.js b/skyquake/plugins/config/server.js
new file mode 100644 (file)
index 0000000..f72199c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+var express = require('express');
+var path = require('path');
+var httpProxy = require('http-proxy');
+var bodyParser = require('body-parser');
+var cors = require('cors');
+var session = require('express-session');
+var proxy = httpProxy.createProxyServer();
+var app = express();
+
+var isProduction = process.env.NODE_ENV === 'production';
+var port = isProduction ? 8080 : 8888;
+var publicPath = path.resolve(__dirname, 'public');
+
+if (!isProduction) {
+
+  //Routes for local development
+  var lpRoutes = require('./routes.js');
+
+  app.use(express.static(publicPath));
+  app.use(session({
+    secret: 'ritio rocks',
+  }));
+  app.use(bodyParser.urlencoded({
+      extended: true
+  }));
+  app.use(bodyParser.json());
+  app.use(cors());
+  app.use('/', lpRoutes);
+  var bundle = require('./server/bundle.js');
+  bundle();
+
+  app.all('/build/*', function (req, res) {
+    proxy.web(req, res, {
+        target: 'http://localhost:8080'
+    });
+  });
+
+}
+proxy.on('error', function(e) {
+  console.log('Could not connect to proxy, please try again...');
+});
+
+app.listen(port, function () {
+  console.log('Server running on port ' + port);
+});
diff --git a/skyquake/plugins/config/src/dashboard/config.scss b/skyquake/plugins/config/src/dashboard/config.scss
new file mode 100644 (file)
index 0000000..3637338
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+@import "style/_colors.scss";
+.Config {
+    -ms-flex: 1 1 100%;
+        flex: 1 1 100%;
+
+    .refreshStatus {
+        margin-left: 0.5rem;
+        padding: 1rem 0;
+
+        .currentStatus {
+            text-transform: uppercase;
+            display:-ms-flexbox;
+            display:flex;
+            -ms-flex-line-pack:center;
+                align-content:center;
+        }
+        span.oi {
+            cursor: pointer;
+        }
+    }
+    .delete, .cancel {
+        cursor: pointer;
+    }
+    .associateSdnAccount {
+        margin: 1rem;
+    }
+    .create > .flex-row > li {
+        -ms-flex: 1 1 auto;
+            flex: 1 1 auto;
+        margin: 0 .25rem;
+    }
+    .create > .flex-row > li:first-child {
+        margin-left: .5rem;
+    }
+    .create > .flex-row > li:last-child {
+        margin-right: .5rem;
+    }
+    /* .flex-row > li h3 {*/
+    /*     background-color: #ffffff;*/
+    /*     padding: 12px 18px;*/
+    /*     text-transform: uppercase;*/
+    /* }*/
+    .create .options {
+        -ms-flex-wrap: wrap;
+            flex-wrap: wrap;
+        text-align: center;
+        margin: 24px auto;
+        width: 90%;
+        flex-wrap: wrap;
+        margin: 24px auto;
+        width: 90%;
+        -ms-flex-pack: start;
+            justify-content: flex-start;
+    }
+    .create .options a {
+        background-color: #ffffff;
+        -ms-flex: 0 1 32%;
+            flex: 0 1 32%;
+        margin: 0 4px 4px 0;
+        padding: 4px;
+        box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
+    }
+    .create .options a h5 {
+        font-size: 10px;
+        margin: 5px 0 0 5px;
+        text-align: left;
+    }
+    .create .options a img {
+        margin-top: 10px;
+        width: 70%;
+    }
+    .list-pools {
+        padding: 24px;
+    }
+    .list-pools li a {
+        background-color: #ffffff;
+        display: block;
+        font-size: 12px;
+        margin-bottom: 24px;
+        padding: 12px;
+        text-align: center;
+        box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
+    }
+    .list-pools li h4 {
+        text-align: left;
+    }
+    .list-pools li img {
+        margin-top: 24px;
+        width: 65%;
+    }
+    .form-actions {
+        clear: both;
+        margin-top: 36px;
+        text-align: center;
+        margin-bottom:1rem;
+    }
+    .form-actions a {
+        color: #000000;
+        display: inline-block;
+        font-size: 12px;
+        padding: 8px 64px;
+        text-decoration: none;
+        text-transform: uppercase;
+        box-shadow: 2px 2px rgba(0, 0, 0, 0.15);
+    }
+    .form-actions a.save {
+        background-color: #ffffff;
+        border: 1px solid #cccccc;
+        border-top: 0;
+        cursor: pointer;
+        margin-right: 48px;
+    }
+    .form-actions a.launch {
+        background-color: #333333;
+        border: 1px solid #000000;
+        border-top: 0;
+        color: #ffffff;
+    }
+    .form-actions a.launch:hover, .form-actions a.launch:active {
+        background: #00acee;
+        color: #ffffff;
+    }
+    .create .select-type {
+        margin: 0.5rem;
+    }
+    /* .create-fleet-pool label {*/
+    /*     padding: 0.125rem;*/
+    /*     display: block;*/
+    /* }*/
+    /* .create-fleet-pool input {*/
+    /*     width: 350px;*/
+    /*     height: 35px;*/
+    /*     margin: 0px 20px 10px 15px;*/
+    /*     box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;*/
+    /*     font-size: 20px;*/
+    /* }*/
+    .create .optional {
+        font-style: italic;
+    }
+    .name-input input {
+      background:white !important;
+      box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;
+      display: block;
+      font-size: 120%;
+      height: 35px;
+      margin: 0;
+      margin-top: 0.25rem;
+      width: 350px;
+    }
+    .select-type {
+        display:-ms-flexbox;
+        display:flex;
+    }
+    .accountSelection {
+        display:-ms-flexbox;
+        display:flex;
+        -ms-flex:0 1;
+            flex:0 1;
+        position:relative;
+        -ms-flex-direction:column;
+        flex-direction:column;
+        margin-right:0.5rem;
+        -ms-flex-align:center;
+        align-items:center;
+        border-top:$gray-light 1px solid;
+        border-left:$gray-light 1px solid;
+        border-right:$gray-dark 1px solid;
+        border-bottom:$gray-dark 1px solid;
+        box-shadow: $gray 2px 2px 3px;
+        cursor:pointer;
+        background:white;
+        &:last-child {
+            margin-right:0;
+        }
+
+        input[type="radio"] {
+            opacity: 0.01;
+            position:absolute;
+            top:0;
+        }
+        &-imageWrapper {
+            display:-ms-flexbox;
+            display:flex;
+            -ms-flex-direction:column;
+                flex-direction:column;
+            margin:0.5rem;
+            padding:0.25rem;
+            background:white;
+            width:100px;
+            height:50px;
+            -ms-flex-align:center;
+                align-items:center;
+            -ms-flex-pack:center;
+                justify-content:center;
+            img{
+                max-width:100px;
+                max-height:50px;
+            }
+        }
+        span {
+            padding-bottom:0.5rem;
+        }
+        &-overlay {
+            position:absolute;
+            top:0;
+            left:0;
+            right:0;
+            bottom:0;
+            height:100%;
+            width:100%;
+            background: rgba(0, 0, 0, 0.25);
+            opacity: 0.2;
+        }
+        &--isSelected{
+            border-bottom:$gray-light 1px solid;
+            border-right:$gray-light 1px solid;
+            border-top:$gray-dark 1px solid;
+            border-left:$gray-dark 1px solid;
+            box-shadow: $gray 2px 2px 3px inset;
+        }
+    }
+    .configForm {
+        margin:0.5rem 0;
+        width:100%;
+        background: $gray-lighter;
+        &-title {
+            background-color: #ffffff;
+            padding: 12px 0.5rem;
+            text-transform: uppercase;
+            &--edit {
+                display:-ms-flexbox;
+                display:flex;
+                -ms-flex-pack:justify;
+                    justify-content:space-between;
+                -ms-flex-align: center;
+                    align-items: center;
+                >div {
+                    display:-ms-flexbox;
+                    display:flex;
+                    -ms-flex-align:center;
+                        align-items:center;
+                }
+            }
+        }
+        img {
+            height:2rem;
+            margin:0 0.5rem;
+        }
+        &-content {
+            padding:0.5rem;
+        }
+        &-nestedParams {
+            display:-ms-flexbox;
+            display:flex;
+            > label {
+                margin-right:0.5rem;
+            }
+            > label:last-child {
+                margin-right:0rem;
+            }
+
+        }
+    }
+    .row {
+        display:-ms-flexbox;
+        display:flex;
+    }
+}
diff --git a/skyquake/plugins/config/src/dashboard/configActions.js b/skyquake/plugins/config/src/dashboard/configActions.js
new file mode 100644 (file)
index 0000000..0956e80
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+module.exports = function(Alt) {
+   return Alt.generateActions(
+                                       'getResourceOrchestratorSuccess',
+                                       'updateResourceOrchestratorSuccess'
+                                       );
+}
diff --git a/skyquake/plugins/config/src/dashboard/configSource.js b/skyquake/plugins/config/src/dashboard/configSource.js
new file mode 100644 (file)
index 0000000..860d1f7
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import $ from 'jquery';
+var Utils = require('utils/utils.js');
+let API_SERVER = require('utils/rw.js').getSearchParams(window.location).api_server;
+let HOST = API_SERVER;
+let NODE_PORT = require('utils/rw.js').getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000);
+let DEV_MODE = require('utils/rw.js').getSearchParams(window.location).dev_mode || false;
+
+if (DEV_MODE) {
+    HOST = window.location.protocol + '//' + window.location.hostname;
+}
+
+
+module.exports = function(Alt) {
+    return {
+        getResourceOrchestrator: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  contentType: "application/json",
+                  success: function(data) {
+                    resolve(data["rw-launchpad:resource-orchestrator"]);
+                  },
+                  error: function(error) {
+                    console.log("There was an error updating the account: ", arguments);
+
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  return reject('error');
+                });
+              });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error retrieving the resource orchestrator information.'
+          }),
+          success: Alt.actions.global.getResourceOrchestratorSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        },
+        update: {
+          remote: function(state, account) {
+
+            return new Promise(function(resolve, reject) {
+              $.ajax({
+                url: 'resource-orchestrator' + '?api_server=' + API_SERVER,
+                type:'PUT',
+                beforeSend: Utils.addAuthorizationStub,
+                data: JSON.stringify(account),
+                contentType: "application/json",
+                success: function(data) {
+                  resolve({data});
+                },
+                error: function(error) {
+                  console.log("There was an error updating the account: ", arguments);
+                  return null;
+                }
+              }).fail(function(xhr){
+                //Authentication and the handling of fail states should be wrapped up into a connection class.
+                Utils.checkAuthentication(xhr.status);
+                return reject('error');
+              });
+
+            });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error updating the account.'
+          }),
+          success: Alt.actions.global.updateResourceOrchestratorSuccess,
+          loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+      }
+    }
+}
+
+function interceptResponse (responses) {
+  return function(data, action, args) {
+    if(responses.hasOwnProperty(data)) {
+      return {
+        type: data,
+        msg: responses[data]
+      }
+    } else {
+      return data;
+    }
+  }
+}
diff --git a/skyquake/plugins/config/src/dashboard/configStore.js b/skyquake/plugins/config/src/dashboard/configStore.js
new file mode 100644 (file)
index 0000000..50a2cb7
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import AccountActions from './configActions.js';
+import AccountSource from './configSource.js';
+
+let tempData = {
+    "rw-launchpad:resource-orchestrator": {
+        "name": "test",
+        "account-type": "openmano",
+        "openmano": {
+            "port": 9090,
+            "tenant-id": "d2581f60-4f28-11e6-9732-fa163e4bfd3e",
+            "host": "10.0.55.39"
+        }
+    }
+}
+
+var rw = require('utils/rw.js');
+var altImage = rw.getSearchParams(window.location).alt_image;
+
+let Params = {
+    //Config Agent
+    ConfigAgent: {
+
+    }
+}
+
+
+
+let AccountMeta = {
+    'account-types': ['openmano', 'rift-ro'],
+    'rift-ro' : [],
+    'openmano' : [{
+        label: "Host",
+        ref: 'host'
+    }, {
+        label: "Port",
+        ref: 'port'
+    }, {
+        label: "Tenant ID",
+        ref: 'tenant-id'
+    }],
+    imageByType: {
+        "openmano": altImage || require("../../images/openmano.png"),
+        "rift-ro": require("../../images/riftio.png")
+
+    },
+    labelByType: {
+        "openmano": "OpenStack",
+        "rift-ro": "Cloudsim"
+    }
+}
+
+export default class AccountStore {
+    constructor() {
+        this.account = {};
+        this.accountType = 'openmano';
+        this.refreshingAll = false;
+        this.sdnOptions = [];
+        this.AccountMeta = AccountMeta;
+        this.bindActions(AccountActions(this.alt));
+        this.registerAsync(AccountSource);
+        this.exportPublicMethods({
+            getROAccount: this.getROAccount,
+            handleParamChange: this.handleParamChange,
+            handleNameChange: this.handleNameChange,
+            handleAccountTypeChange: this.handleAccountTypeChange,
+            updateAccount: this.updateAccount,
+            getImage: this.getImage
+        })
+    }
+    setAccountTemplate = (AccountType) => {
+        let state = {};
+        let account = {
+            name: '',
+            'account-type': AccountType || 'rift-ro',
+        };
+        account[AccountType || 'rift-ro'] = {};
+        state.account = account;
+        state.accountType = AccountType;
+        if (AccountType == this.initialAccountType) {
+            state.account = this.initialAccount;
+        }
+        this.setState(state)
+    }
+    updateAccount = (account) => {
+        this.setState({account:account})
+    }
+    getROAccount = () => {
+        let data = tempData["rw-launchpad:resource-orchestrator"]
+        this.setState({
+            account: data
+        })
+    }
+    handleNameChange = (event) => {
+        var account = this.account;
+        account.name = event.target.value;
+        this.setState(
+             {
+                account:account
+             }
+        );
+    }
+    handleAccountTypeChange = (event) => {
+        var accountType = event.target.value;
+        this.setAccountTemplate(accountType);
+    }
+    handleParamChange(node, event) {
+        return function(event) {
+            var account = this.state.account;
+            account[account['account-type']][node.ref] = event.target.value;
+            this.updateAccount(account);
+        }.bind(this);
+    }
+    getImage = (type) => {
+        return AccountMeta.imageByType[type];
+    }
+    getResourceOrchestratorSuccess = (data) => {
+        this.alt.actions.global.hideScreenLoader.defer();
+        if(data.hasOwnProperty('empty')) {
+            this.setAccountTemplate(false)
+        } else {
+            this.setState({
+                initialAccount: data,
+                initialAccountType: data['account-type'],
+                account: data,
+                accountType: data['account-type'] || 'rift-ro'
+            });
+        }
+    }
+    updateResourceOrchestratorSuccess = (data) => {
+        this.alt.actions.global.showNotification.defer({type:'success', msg: 'Resource Orchestrator has been succesfully updated'});
+    }
+}
diff --git a/skyquake/plugins/config/src/dashboard/dashboard.jsx b/skyquake/plugins/config/src/dashboard/dashboard.jsx
new file mode 100644 (file)
index 0000000..b8a743f
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import AppHeader from 'widgets/header/header.jsx';
+import ConfigStore from './configStore.js';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import 'style/layout.scss';
+import './config.scss';
+import {Panel, PanelWrapper} from 'widgets/panel/panel';
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import Button from 'widgets/button/rw.button.js';
+
+class ConfigDashboard extends React.Component {
+    constructor(props) {
+        super(props);
+        this.Store = this.props.flux.stores.hasOwnProperty('ConfigStore') ? this.props.flux.stores.ConfigStore : this.props.flux.createStore(ConfigStore);
+        this.state = this.Store.getState();
+    }
+    componentWillMount() {
+        this.Store.listen(this.updateState);
+        this.Store.getResourceOrchestrator();
+    }
+    componentWillUnmount() {
+        this.Store.unlisten(this.updateState);
+    }
+    updateState = (state) => {
+        this.setState(state);
+    }
+    updateAccount = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.Store.update(this.state.account);
+    }
+    render() {
+        let self = this;
+        let html;
+        let Account = this.state.account;
+        let AccountMeta = this.state.AccountMeta;
+        let AccountType = this.state.accountType;
+        let isEdit = true;
+        let ParamsHTML = null;
+        let Store = this.Store;
+        let Types = this.state.AccountMeta['account-types'];
+
+        let selectAccountStack = [];
+        let selectAccountHTML = null;
+
+
+        if (Account['account-type']) {
+            for (var i = 0; i < Types.length; i++) {
+                var node = Types[i];
+                var isSelected = (Account['account-type'] == node);
+                selectAccountStack.push(
+                  <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
+                    <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
+                    <div className="accountSelection-imageWrapper">
+                        <img src={Store.getImage(node)}/>
+                    </div>
+                    <input type="radio" name="account"
+                        onChange={Store.handleAccountTypeChange} defaultChecked={node == Types[0]} value={node} />{node}
+                  </label>
+                )
+            }
+            selectAccountHTML = (
+                <Panel className="accountForm" title="Select Account Type" no-corners>
+                    <div className="select-type accountForm-content row" >
+                        {selectAccountStack}
+                    </div>
+                </Panel>
+            );
+        }
+
+        if (AccountMeta[AccountType] && AccountMeta[AccountType].length > 0) {
+            var paramsStack = [];
+            var optionalField = '';
+            for (var i = 0; i < AccountMeta[AccountType].length; i++) {
+                var node = AccountMeta[AccountType][i];
+                var value = ""
+                if (Account[AccountType]) {
+                    value = Account[AccountType][node.ref]
+                }
+                paramsStack.push(
+                    <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional}  onChange={this.Store.handleParamChange(node)} value={value} />
+                );
+            }
+            ParamsHTML = (
+                <Panel className="create-fleet-pool accountForm" title={(isEdit ? 'Update' : 'Enter') +  ' Account Details'} no-corners>
+                    <div className="accountForm-content">
+                        {paramsStack}
+                    </div>
+                </Panel>
+            )
+        } else {
+            ParamsHTML = (
+                <Panel className="create-fleet-pool accountForm" title={(isEdit ? 'Update' : 'Enter') +  ' Account Details'} no-corners>
+                    <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
+                </Panel>
+            )
+        }
+
+
+
+        html = (
+            <PanelWrapper className="column Config" style={{'alignContent': 'center', 'flexDirection': 'column'}}>
+            <form className="app-body create Accounts"  onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
+                <div className="noticeSubText noticeSubText_right">
+                    * required
+                </div>
+                <div>
+                    <Panel className="create-fleet-pool accountForm" title="Resource Orchestrator" no-corners>
+                        <div className="accountForm-content">
+                             <TextInput className="accountForm-input" label={"Name"} onChange={this.Store.handleNameChange} value={Account.name} />
+                        </div>
+                    </Panel>
+
+                            {
+                                selectAccountHTML
+                            }
+                            {
+                                ParamsHTML
+                            }
+                </div>
+                <div className="form-actions">
+                    <Button  className="light" label="Cancel" />
+                    <Button key="4" role="button" className="update dark" label="Update"  onClick={this.updateAccount} />
+                </div>
+            </form>
+            </PanelWrapper>
+        );
+        return html;
+    }
+}
+// onClick={this.Store.update.bind(null, Account)}
+ConfigDashboard.contextTypes = {
+    router: React.PropTypes.object
+};
+
+export default SkyquakeComponent(ConfigDashboard);
diff --git a/skyquake/plugins/config/src/dashboard/inputs.jsx b/skyquake/plugins/config/src/dashboard/inputs.jsx
new file mode 100644 (file)
index 0000000..457a08e
--- /dev/null
@@ -0,0 +1,401 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+
+import React from 'react';
+import Button from 'widgets/button/rw.button.js';
+import _ from 'lodash';
+import SkyquakeComponent from 'widgets/skyquake_container/skyquakeComponent.jsx';
+import Crouton from 'react-crouton';
+import TextInput from 'widgets/form_controls/textInput.jsx';
+import 'style/common.scss';
+import './config.scss';
+class Account extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {};
+        this.state.account = {};
+    }
+    storeListener = (state) => {
+        if(!state.account) {
+            this.setUp(this.props)
+        }
+        state.account && this.setState({account: state.account,accountType: state.accountType, types: state.types, sdnOptions: state.sdnOptions})
+    }
+    componentWillMount() {
+        this.props.store.listen(this.storeListener);
+        this.setUp(this.props);
+    }
+    componentWillReceiveProps(nextProps) {
+        if(JSON.stringify(nextProps.params) != JSON.stringify(this.props.params)){
+              this.setUp(nextProps);
+        }
+    }
+    componentWillUnmount() {
+        this.props.store.unlisten(this.storeListener);
+    }
+    setUp(props){
+        if(props.params.name != 'create') {
+            this.props.store.viewAccount({type: props.params.type, name: props.params.name});
+        } else {
+            this.props.store.setAccountTemplate(props.params.type);
+        }
+    }
+    create(e) {
+        e.preventDefault();
+        var self = this;
+        var Account = this.state.account;
+        let AccountType = this.state.accountType;
+        if (Account.name == "") {
+            self.props.flux.actions.global.showNotification("Please give the account a name");
+            return;
+        } else {
+            var type = Account['account-type'];
+            var params = Account.params;
+
+            if(params) {
+                for (var i = 0; i < params.length; i++) {
+                    var param = params[i].ref;
+                    if (typeof(Account[type]) == 'undefined' || typeof(Account[type][param]) == 'undefined' || Account[type][param] == "") {
+                        if (!params[i].optional) {
+                            self.props.flux.actions.global.showNotification("Please fill all account details");
+                            return;
+                        }
+                    }
+                }
+            }
+
+            let nestedParams = Account.nestedParams && Account.nestedParams;
+            if (nestedParams && nestedParams.params) {
+                for (let i = 0; i < nestedParams.params.length; i++) {
+                    let nestedParam = nestedParams.params[i].ref;
+                    if (typeof(Account[type]) == 'undefined' || typeof(Account[type][nestedParams['container-name']][nestedParam]) == 'undefined' || Account[type][nestedParams['container-name']][nestedParam] == "") {
+                        if (!nestedParams.params[i].optional) {
+                            self.props.flux.actions.global.showNotification("Please fill all account details");
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+
+        let newAccount = _.cloneDeep(removeTrailingWhitespace(Account));
+        delete newAccount.params;
+        newAccount.nestedParams &&
+            newAccount.nestedParams['container-name'] &&
+            delete newAccount[newAccount.nestedParams['container-name']];
+        delete newAccount.nestedParams;
+
+        this.props.flux.actions.global.showScreenLoader();
+        this.props.store.create(newAccount, AccountType).then(function() {
+            self.props.router.push({pathname:'accounts'});
+            self.props.flux.actions.global.hideScreenLoader.defer();
+        },
+         function() {
+            self.props.flux.actions.global.showNotification("There was an error creating your account. Please contact your system administrator.");
+            self.props.flux.actions.global.hideScreenLoader.defer();
+         });
+    }
+    update(e) {
+        e.preventDefault();
+        var self = this;
+        var Account = this.state.account;
+        let AccountType = this.state.accountType;
+        this.props.flux.actions.global.showScreenLoader();
+        this.props.store.update(Account, AccountType).then(function() {
+            self.props.router.push({pathname:'accounts'});
+             self.props.flux.actions.global.hideScreenLoader();
+        },
+        function() {
+
+        });
+    }
+    cancel = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.props.router.push({pathname:'accounts'});
+    }
+    handleDelete = () => {
+        let self = this;
+        let msg = 'Preparing to delete "' + self.state.account.name + '"' +
+        ' Are you sure you want to delete this ' + self.state.accountType + ' account?"';
+        if (window.confirm(msg)) {
+            this.props.store.delete(self.state.accountType, self.state.account.name).then(function() {
+                self.props.flux.actions.global.hideScreenLoader();
+                self.props.router.push({pathname:'accounts'});
+            }, function(){
+                // self.props.flux.actions.global.hideScreenLoader.defer();
+                // console.log('Delete Account Fail');
+            });
+        } else {
+           self.props.flux.actions.global.hideScreenLoader();
+        }
+    }
+    handleNameChange(event) {
+       this.props.store.handleNameChange(event);
+    }
+    handleAccountTypeChange(node, event) {
+        this.props.store.handleAccountTypeChange(node, event);
+    }
+    handleSelectSdnAccount = (e) => {
+        var tmp = this.state.account;
+        if(e) {
+            tmp['sdn-account'] = e;
+        } else {
+            if(tmp['sdn-account']) {
+                delete tmp['sdn-account'];
+            }
+        }
+        console.log(e, tmp)
+    }
+    preventDefault = (e) => {
+        e.preventDefault();
+        e.stopPropagation();
+    }
+    evaluateSubmit = (e) => {
+        if (e.keyCode == 13) {
+            if (this.props.edit) {
+                this.update(e);
+            } else {
+                this.create(e);
+            }
+            e.preventDefault();
+            e.stopPropagation();
+        }
+    }
+
+    render() {
+        let self = this;
+        let {store, ...props} = this.props;
+        // This section builds elements that only show up on the create page.
+        // var name = <label>Name <input type="text" onChange={this.handleNameChange.bind(this)} style={{'textAlign':'left'}} /></label>;
+        var name = <TextInput label="Name"  onChange={this.handleNameChange.bind(this)} required={true} />;
+        let params = null;
+        let selectAccount = null;
+        let resfreshStatus = null;
+        let Account = this.state.account;
+        // AccountType is for the view, not the data account-type value;
+        let AccountType = this.state.accountType;
+        let Types = this.state.types;
+        let isEdit = this.props.params.name != 'create';
+        var buttons;
+        let cloudResources = Account['cloud-resources-state'] && Account['cloud-resources-state'][Account['account-type']];
+        let cloudResourcesStateHTML = null;
+
+        // Account Type Radio
+        var selectAccountStack = [];
+        if (!isEdit) {
+            buttons = [
+                <Button key="0" onClick={this.cancel} className="cancel light" label="Cancel"></Button>,
+                <Button key="1" role="button" onClick={this.create.bind(this)} className="save dark" label="Save" />
+            ]
+            for (var i = 0; i < Types.length; i++) {
+                var node = Types[i];
+                var isSelected = (Account['account-type'] == node['account-type']);
+                selectAccountStack.push(
+                  <label key={i} className={"accountSelection " + (isSelected ? "accountSelection--isSelected" : "")}>
+                    <div className={"accountSelection-overlay" + (isSelected ? "accountSelection-overlay--isSelected" : "")}></div>
+                    <div className="accountSelection-imageWrapper">
+                        <img src={store.getImage(node['account-type'])}/>
+                    </div>
+                    <input type="radio" name="account" onChange={this.handleAccountTypeChange.bind(this, node)} defaultChecked={node.name == Types[0].name} value={node['account-type']} />{node.name}
+                  </label>
+                )
+            }
+            selectAccount = (
+                <div className="accountForm">
+                    <h3 className="accountForm-title">Select Account Type</h3>
+                    <div className="select-type accountForm-content" >
+                        {selectAccountStack}
+                    </div>
+                </div>
+            );
+        } else {
+            selectAccount = null
+        }
+
+         //
+        // This sections builds the parameters for the account details.
+        if (Account.params) {
+            var paramsStack = [];
+            var optionalField = '';
+            for (var i = 0; i < Account.params.length; i++) {
+                var node = Account.params[i];
+                var value = ""
+                if (Account[Account['account-type']]) {
+                    value = Account[Account['account-type']][node.ref]
+                }
+                if (this.props.edit && Account.params) {
+                    value = Account.params[node.ref];
+                }
+                paramsStack.push(
+                    <TextInput key={node.label} className="accountForm-input" label={node.label} required={!node.optional}  onChange={this.props.store.handleParamChange(node)} value={value} />
+                );
+            }
+            params = (
+                <li className="create-fleet-pool accountForm">
+                    <h3  className="accountForm-title"> {isEdit ? 'Update' : 'Enter'} Account Details</h3>
+                    <div className="accountForm-content">
+                        {paramsStack}
+                    </div>
+                </li>
+            )
+        } else {
+            params = (
+                <li className="create-fleet-pool accountForm">
+                    <h3 className="accountForm-title"> {isEdit ? 'Update' : 'Enter'}</h3>
+                    <label style={{'marginLeft':'17px', color:'#888'}}>No Details Required</label>
+                </li>
+            )
+        }
+
+        // This section builds elements that only show up in the edit page.
+        if (isEdit) {
+            name = <label>{Account.name}</label>;
+            buttons = [
+                <Button key="2" onClick={this.handleDelete} className="light" label="Remove Account" />,
+                <Button key="3" onClick={this.cancel} className="light" label="Cancel" />,
+                <Button key="4" role="button" onClick={this.update.bind(this)} className="update dark" label="Update" />
+            ];
+            resfreshStatus = Account['connection-status'] ? (
+                <div className="accountForm">
+                    <div className="accountForm-title accountForm-title--edit">
+                        Connection Status
+                    </div>
+                    <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
+                        <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
+                            <AccountConnectivityStatus status={Account['connection-status'].status} />
+                            {Account['connection-status'].status.toUpperCase()}
+                        </div>
+                            <Button className="refreshList light" onClick={this.props.store.refreshAccount.bind(this, Account.name, AccountType)} label="REFRESH STATUS"></Button>
+                    </div>
+                    {
+                        Account['connection-status'].status.toUpperCase() === 'FAILURE' ?
+                        displayFailureMessage(Account['connection-status'].details) : null
+                    }
+                </div>
+            ) : null;
+            // cloudResourcesStateHTML = (
+            //     <div className="accountForm">
+            //         <h3 className="accountForm-title">Resources Status</h3>
+            //         <div className="accountForm-content" >
+            //         <ul>
+            //             {
+            //                 cloudResources && props.AccountMeta.resources[Account['account-type']].map(function(r, i) {
+
+            //                     return (
+            //                         <li key={i}>
+            //                             {r}: {cloudResources[r]}
+            //                         </li>
+            //                     )
+            //                 }) || 'No Additional Resources'
+            //             }
+            //         </ul>
+            //         </div>
+            //     </div>
+            // )
+        }
+
+        var html = (
+
+              <form className="app-body create Accounts"  onSubmit={this.preventDefault} onKeyDown={this.evaluateSubmit}>
+                <div className="noticeSubText noticeSubText_right">
+                    * required
+                </div>
+                <div className="associateSdnAccount accountForm">
+                    <h3 className="accountForm-title">Account</h3>
+                    <div className="accountForm-content" style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
+                        <h4 style={{flex: '1'}}>{name}</h4>
+                        { isEdit ?
+                            (
+                                <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
+                                    <img src={store.getImage(Account['account-type'])}/> {props.AccountMeta.labelByType[Account['account-type']]}
+                                </div>)
+                            : null
+                        }
+                    </div>
+                </div>
+
+                  {selectAccount}
+                  {sdnAccounts}
+                  {resfreshStatus}
+                  {cloudResourcesStateHTML}
+                  <ol className="flex-row">
+                      {params}
+                  </ol>
+                  <div className="form-actions">
+                      {buttons}
+                  </div>
+              </form>
+        )
+        return html;
+    }
+}
+
+function displayFailureMessage(msg) {
+    return (
+        <div className="accountForm-content" style={{maxWidth: '600px'}}>
+            <div style={{paddingBottom: '1rem'}}>Details:</div>
+            <div>
+                {msg}
+            </div>
+
+        </div>
+    )
+}
+
+class SelectOption extends React.Component {
+  constructor(props){
+    super(props);
+  }
+  handleOnChange = (e) => {
+    this.props.onChange(JSON.parse(e.target.value));
+  }
+  render() {
+    let html;
+    html = (
+      <select className={this.props.className} onChange={this.handleOnChange}>
+        {
+          this.props.options.map(function(op, i) {
+            return <option key={i} value={JSON.stringify(op.value)}>{op.label}</option>
+          })
+        }
+      </select>
+    );
+    return html;
+  }
+}
+SelectOption.defaultProps = {
+  options: [],
+  onChange: function(e) {
+    console.dir(e)
+  }
+}
+
+function removeTrailingWhitespace(Account) {
+             var type = Account['account-type'];
+            var params = Account.params;
+
+            if(params) {
+                for (var i = 0; i < params.length; i++) {
+                    var param = params[i].ref;
+                    if(typeof(Account[type][param]) == 'string') {
+                        Account[type][param] = Account[type][param].trim();
+                    }
+                }
+            }
+
+            let nestedParams = Account.nestedParams;
+            if (nestedParams && nestedParams.params) {
+                for (let i = 0; i < nestedParams.params.length; i++) {
+                    let nestedParam = nestedParams.params[i].ref;
+                    let nestedParamValue = Account[type][nestedParams['container-name']][nestedParam];
+                    if (typeof(nestedParamValue) == 'string') {
+                        Account[type][nestedParams['container-name']][nestedParam] = nestedParamValue.trim();
+                    }
+                }
+            }
+            return Account;
+}
+
+export default SkyquakeComponent(Account)
diff --git a/skyquake/plugins/config/src/main.js b/skyquake/plugins/config/src/main.js
new file mode 100644 (file)
index 0000000..5dc626f
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+import "babel-polyfill";
+import { render } from 'react-dom';
+import SkyquakeRouter from 'widgets/skyquake_container/skyquakeRouter.jsx';
+const config = require('json!../config.json');
+
+let context = require.context('./', true, /^\.\/.*\.jsx$/);
+let router = SkyquakeRouter(config, context);
+let element = document.querySelector('#app');
+
+render(router, element);
+
+
diff --git a/skyquake/plugins/config/webpack.production.config.js b/skyquake/plugins/config/webpack.production.config.js
new file mode 100644 (file)
index 0000000..49ad631
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * STANDARD_RIFT_IO_COPYRIGHT
+ */
+var Webpack = require('webpack');
+var path = require('path');
+var nodeModulesPath = path.resolve(__dirname, 'node_modules');
+var buildPath = path.resolve(__dirname, 'public', 'build');
+var mainPath = path.resolve(__dirname, 'src', 'main.js');
+var uiPluginCmakeBuild = process.env.ui_plugin_cmake_build || false;
+var frameworkPath = uiPluginCmakeBuild?'../../../../skyquake/skyquake-build/framework':'../../framework';
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var CommonsPlugin = new require("webpack/lib/optimize/CommonsChunkPlugin")
+// Added to overcome node-sass bug https://github.com/iam4x/isomorphic-flux-boilerplate/issues/62
+process.env.UV_THREADPOOL_SIZE=64;
+var config = {
+    devtool: 'source-map',
+    entry: mainPath,
+    output: {
+        path: buildPath,
+        filename: 'bundle.js',
+        publicPath: "build/"
+    },
+    resolve: {
+        extensions: ['', '.js', '.jsx', '.css', '.scss'],
+        root: path.resolve(frameworkPath),
+        alias: {
+            'widgets': path.resolve(frameworkPath) + '/widgets',
+            'style':  path.resolve(frameworkPath) + '/style',
+            'utils':  path.resolve(frameworkPath) + '/utils'
+        }
+    },
+    module: {
+        loaders: [{
+                test: /\.(jpe?g|png|gif|svg|ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/i,
+                loader: "file-loader"
+            },
+            {
+                test: /\.(js|jsx)$/,
+                exclude: /react-treeview/,
+                loader: 'babel-loader',
+                query: {
+                    presets: ["es2015", "stage-0", "react"]
+                }
+            }, {
+                test: /\.css$/,
+                loader: 'style!css'
+            }, {
+                test: /\.scss/,
+                loader: 'style!css!sass?includePaths[]='+ path.resolve(frameworkPath)
+            }
+        ]
+    },
+    plugins: [
+        new HtmlWebpackPlugin({
+            filename: '../index.html'
+            , templateContent: '<div id="app"></div>'
+        }),
+        new Webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+    ]
+};
+module.exports = config;
index 3972289..96818e0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -1906,7 +1906,7 @@ DataCenters.get = function(req) {
                 'Authorization': req.get('Authorization')
             });
         request({
-            url: utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters/cloud-accounts?deep',
+            url: utils.confdPort(api_server) + APIVersion + '/api/operational/datacenters?deep',
             method: 'GET',
             headers: requestHeaders,
             forever: constants.FOREVER_ON,
@@ -1915,7 +1915,7 @@ DataCenters.get = function(req) {
             if (utils.validateResponse('DataCenters.get', error, response, body, resolve, reject)) {
                 var returnData = {};
                 try {
-                    data = JSON.parse(response.body)['rw-launchpad:cloud-accounts'];
+                    data = JSON.parse(response.body)["rw-launchpad:datacenters"]["ro-accounts"];
                     data.map(function(c) {
                         returnData[c.name] = c.datacenters;
                     })
index 607576b..9c358e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -40,8 +40,10 @@ class InstantiateDashboard extends React.Component {
         asyncOperations.push(this.Store.getCatalog());
         asyncOperations.push(this.Store.getCloudAccount(function() {
           asyncOperations.push(self.Store.getDataCenters());
+          asyncOperations.push(self.Store.getResourceOrchestrator());
           asyncOperations.push(self.Store.getSshKey());
           asyncOperations.push(self.Store.getConfigAgent());
+          asyncOperations.push(self.Store.getResourceOrchestrator());
         }));
         Promise.all(asyncOperations).then(function(resolve, reject) {
             if(self.props.params.nsd) {
@@ -75,7 +77,7 @@ class InstantiateDashboard extends React.Component {
           self.props.actions.showNotification('Spaces and special characters except underscores are not supported in the network service name at this time');
           return;
         }
-        if (this.isOpenMano() && (this.state.dataCenterID == "" || !this.state.dataCenterID)) {
+        if (this.state.isOpenMano && (this.state.dataCenterID == "" || !this.state.dataCenterID)) {
              self.props.actions.showNotification("Please enter the Data Center ID");
           return;
         }
@@ -87,7 +89,7 @@ class InstantiateDashboard extends React.Component {
         return !this.props.location.pathname.split('/')[2];
     }
     isOpenMano = () => {
-        return this.state.selectedCloudAccount['account-type'] == 'openmano';
+        return this.state.ro['account-type'] == 'openmano';
     }
     openDescriptor = (descriptor) => {
         let NSD = descriptor;
index 00397ed..87c9592 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,11 +33,20 @@ export default class InstantiateInputParams extends Component {
       <div className="configure-nsd_section">
         <div className="inputControls">
             <TextInput label="Instance Name" type="text" pattern="^[a-zA-Z0-9_]*$" style={{textAlign:'left'}} onChange={props.updateName} value={props.name}/>
-          <label>Select VIM Account
-            <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} onChange={props.nsFn.updateSelectedCloudAccount} />
-          </label>
           {
-            isOpenMano(props.selectedCloudAccount) ? this.dataCentersHTML(props.dataCenters[selectedCloudAccount.name], props.nsFn.updateSelectedDataCenter) : null
+            !isOpenMano(props.ro) ?
+              (
+                <label>Select VIM Account
+                  <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} onChange={props.nsFn.updateSelectedCloudAccount} />
+                </label>
+              )
+            : null
+          }
+          {
+            isOpenMano(props.ro) ?
+              dataCentersHTML(props.dataCenters[props.ro.name],
+                              props.nsFn.updateSelectedDataCenter)
+              : null
           }
         </div>
       </div>
@@ -60,11 +69,21 @@ export default class InstantiateInputParams extends Component {
                 return (
                     <div className="inputControls" key={i}>
                     <h4 className="inputControls-title">VNFD: {v.name}</h4>
-                      <label>Select VIM Account
-                        <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} initial={true} onChange={props.vnfFn.updateSelectedCloudAccount.bind(v['member-vnf-index'])} defaultValue={defaultValue} />
-                      </label>
+                  {
+                    !isOpenMano(props.ro) ?
+                      (
+                        <label>Select VIM Account
+                          <SelectOption options={constructCloudAccountOptions(props.cloudAccounts)} initial={true} onChange={props.vnfFn.updateSelectedCloudAccount.bind(v['member-vnf-index'])} defaultValue={defaultValue} />
+                        </label>
+                      )
+                    : null
+                  }
                       {
-                        isOpenMano(defaultValue) ? dataCentersHTML(dataCenters[defaultValue.account.name], props.vnfFn.updateSelectedDataCenter(v['member-vnf-index'])) : null
+                        isOpenMano(props.ro) ?
+                          dataCentersHTML(
+                                          props.dataCenters[props.ro.name],
+                                          props.vnfFn.updateSelectedDataCenter.bind(null, v['member-vnf-index']))
+                          : null
                       }
                       {
                         (props.configAgentAccounts && props.configAgentAccounts.length > 0) ?
@@ -548,22 +567,17 @@ function constructCloudAccountOptions(cloudAccounts){
   });
   return CloudAccountOptions;
 }
-function dataCentersHTML(state, onChange) {
+function dataCentersHTML(dataCenters, onChange) {
   //Build DataCenter options
   //Relook at this, why is it an object?
-  let dataCenters = state.dataCenters || [];
-  let DataCenterOptions = {};
-  if(dataCenters){
-    for (let d in dataCenters) {
-      DataCenterOptions[d] = dataCenters[d].map(function(dc, index) {
-        return {
-          label: dc.name,
-          value: dc.uuid
-        }
-      });
+  let DataCenterOptions = [];
+  DataCenterOptions = dataCenters && dataCenters.map(function(dc, index) {
+    return {
+      label: dc.name,
+      value: dc.uuid
     }
-  }
-  if (dataCenters.length > 0) {
+  });
+  if (dataCenters && dataCenters.length > 0) {
     return (
       <label>Select Data Center
         <SelectOption options={DataCenterOptions} onChange={onChange} />
@@ -577,9 +591,6 @@ function isOpenMano(account) {
     if (a.constructor.name == 'String') {
       a = JSON.parse(a);
     }
-    if(a.hasOwnProperty('account')) {
-      a = a.account;
-    }
     return a['account-type'] == 'openmano';
   } else {
     return false;
index 47bce91..5546b25 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -63,6 +63,7 @@ class Instantiate extends Component {
             cloudAccounts={this.props.cloudAccounts}
             selectedCloudAccount={this.props.selectedCloudAccount}
             vnfdCloudAccounts={this.props.vnfdCloudAccounts}
+            ro={this.props.ro}
             dataCenters={this.props.dataCenters}
             configAgentAccounts={this.props.configAgentAccounts}
             inputParameters={this.props['input-parameters']}
@@ -83,6 +84,8 @@ class Instantiate extends Component {
             selectedID={this.props.selectedNSDid}
             selectedNSD={selectedNSD}
 
+            isOpenMano={this.props.isOpenMano}
+
         /></Panel>
         </PanelWrapper>
     )
index eafac26..6b7941e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -39,12 +39,13 @@ class LaunchNetworkServiceStore {
         this.selectedNSDid;
         this.selectedNSD = {};
         this.selectedCloudAccount = {};
-        this.dataCenters = {};
+        this.dataCenters = [];
         this.cloudAccounts = [];
         this.isLoading = false;
         this.hasConfigureNSD = false;
         this['input-parameters'] = [];
         this.displayPlacementGroups = false;
+        this.ro = {};
         this.bindActions(NetworkServiceActions);
         this.nsdConfiguration = {
             name:'',
@@ -59,6 +60,7 @@ class LaunchNetworkServiceStore {
         this.configAgentAccounts = [];
 
         this.isPreviewing = false;
+        this.isOpenMano = false;
         this.registerAsync(NetworkServiceSource);
         this.exportPublicMethods({
             getMockData: getMockData.bind(this),
@@ -148,12 +150,14 @@ class LaunchNetworkServiceStore {
             configAgentAccounts: configAgentAccounts
         })
     }
-    getDataCentersSuccess(dataCenters) {
+    getDataCentersSuccess(data) {
+        let dataCenters = data;
+
         let newState = {
-            dataCenters: dataCenters
+            dataCenters: dataCenters || []
         };
-        if (this.selectedCloudAccount['account-type'] == 'openmano') {
-            newState.dataCenterID = dataCenters[this.selectedCloudAccount.name][0].uuid
+        if (this.ro['account-type'] == 'openmano') {
+            newState.dataCenterID = dataCenters[this.ro.name][0].uuid
         }
         this.setState(newState)
     }
@@ -191,6 +195,15 @@ class LaunchNetworkServiceStore {
             sshKeysRef: []
         })
     }
+    getResourceOrchestratorSuccess = (data) => {
+        Alt.actions.global.hideScreenLoader.defer();
+        this.setState({
+            ro: data
+        })
+    }
+    getResourceOrchestratorError = (data) => {
+        console.log('getResourceOrchestrator Error: ', data)
+    }
     //Form handlers
     nameUpdated = (e) => {
         this.setState({
@@ -276,15 +289,11 @@ class LaunchNetworkServiceStore {
                 } else {
                      newState.displayPlacementGroups = false;
                 }
-                if (cloudAccount['account-type'] == 'openmano' && this.dataCenters && self.dataCenters[cloudAccount['name']]) {
-                    let datacenter = self.dataCenters[cloudAccount['name']][0];
-                    newState.dataCenterID = datacenter.uuid;
-                }
                 self.setState(newState);
             },
             updateSelectedDataCenter: (dataCenter) => {
                 self.setState({
-                    dataCenterID: dataCenter
+                    dataCenterID: dataCenter.target.value
                 });
             },
             placementGroupUpdate: (i, k, value) => {
@@ -409,7 +418,10 @@ class LaunchNetworkServiceStore {
             },
             updateSelectedDataCenter: (id, dataCenter) => {
                 let vnfCA = self.vnfdCloudAccounts;
-                vnfCA[id].datacenter = dataCenter;
+                if (!vnfCA[id]) {
+                    vnfCA[id] = {};
+                }
+                vnfCA[id].datacenter = dataCenter.target.value;
                 self.setState({
                     vnfdCloudAccounts: vnfCA
                 });
@@ -645,9 +657,11 @@ class LaunchNetworkServiceStore {
             "admin-status": launch ? "ENABLED" : "DISABLED",
             "nsd": nsdPayload
         }
-        payload["cloud-account"] = this.state.selectedCloudAccount.name;
-        if (this.state.selectedCloudAccount['account-type'] == "openmano") {
+
+        if (this.state.ro['account-type'] == 'openmano') {
             payload['om-datacenter'] = this.state.dataCenterID;
+        } else {
+            payload["cloud-account"] = this.state.selectedCloudAccount.name;
         }
         if (this.state.hasConfigureNSD) {
             let ips = this.state['input-parameters'];
index 7f44bfe..a769246 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,5 +36,7 @@ export default Alt.generateActions(
                                    'getInstantiateSshKeyLoading',
                                    'getInstantiateSshKeyError',
                                    'getConfigAgentSuccess',
-                                   'getConfigAgentError'
+                                   'getConfigAgentError',
+                                   'getResourceOrchestratorSuccess',
+                                   'getResourceOrchestratorAgentError'
                                    )
index d2d950f..3a8886c 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -190,6 +190,48 @@ export default function(Alt){
       },
       success: Alt.actions.global.getConfigAgentSuccess,
       error: Alt.actions.global.getConfigAgentError
+  },
+  getResourceOrchestrator: {
+          remote: function() {
+              return new Promise(function(resolve, reject) {
+                $.ajax({
+                  url: 'passthrough/data/api/running/resource-orchestrator' + '?api_server=' + API_SERVER,
+                  type: 'GET',
+                  beforeSend: Utils.addAuthorizationStub,
+                  contentType: "application/json",
+                  success: function(data) {
+                    resolve(data["rw-launchpad:resource-orchestrator"]);
+                  },
+                  error: function(error) {
+                    console.log("There was an error updating the account: ", arguments);
+
+                  }
+                }).fail(function(xhr){
+                  //Authentication and the handling of fail states should be wrapped up into a connection class.
+                  Utils.checkAuthentication(xhr.status);
+                  return reject('error');
+                });
+              });
+          },
+          interceptResponse: interceptResponse({
+            'error': 'There was an error retrieving the resource orchestrator information.'
+          }),
+          success: Alt.actions.global.getResourceOrchestratorSuccess,
+                    loading: Alt.actions.global.showScreenLoader,
+          error: Alt.actions.global.showNotification
+        }
+
   }
 }
+function interceptResponse (responses) {
+  return function(data, action, args) {
+    if(responses.hasOwnProperty(data)) {
+      return {
+        type: data,
+        msg: responses[data]
+      }
+    } else {
+      return data;
+    }
+  }
 }
index 4052ebb..431f745 100644 (file)
@@ -1,6 +1,6 @@
 
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
@@ -40,5 +40,6 @@ module.exports = Alt.generateActions(
                                        'instantiateNetworkService',
                                        'setNsListPanelVisible',
                                        'getVDUConsoleLinkSuccess',
-                                       'getVDUConsoleLinkError'
+                                       'getVDUConsoleLinkError',
+                                       'getResourceOrchestratorSuccess'
                                        );
index dac8a82..0e9b34e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 
+ *
  *   Copyright 2016 RIFT.IO Inc
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");