Merge "[CORD-1947] veestat required packages" into cord-4.1
diff --git a/docs/developer/gui_extensions.md b/docs/developer/gui_extensions.md
index b299283..4bb6108 100644
--- a/docs/developer/gui_extensions.md
+++ b/docs/developer/gui_extensions.md
@@ -1,23 +1,45 @@
-# Create a custom GUI Extension
+# Creating a custom GUI Extension
The CORD GUI is designed to be extensible. There are two cases in which we envision an extension to be needed:
- Provide a different view over data
- Create custom interfaces for services
-The development process for the two cases it's absolutely the same.
+The development process for both is the same.
-We suggest to get started by duplicating [xos-sample-gui-extension](https://github.com/opencord/xos-sample-gui-extension)
-
-## Development
-
-The development environment is the same as `xos-gui` so you can use the `npm start` command and augment it with the same sets fo environment variables.
-The dev server is going to proxy your requests to the appropriate backend and load the base application from it.
+## Starting Development
+
+### Option 1: Use the provided Yeoman xos-gui-extension generator
+
+The provided generator in `generator-xos-gui-extension` will generate a new GUI extension for you with the name of
+your choice and demo components based on a simplified `sample-gui-extension`. No refactoring necessary.
+
+#### Prerequisites
+You must have the Yeoman toolset installed via npm on your system. It can be installed by running `npm install --global yo`.
+If you encounter any issues, full detailed instructions on installation can be found at the [Yeoman website](http://yeoman.io/codelab/setup.html).
+
+#### Installation
+Once you have successfully installed Yeoman, run the following to install the generator.
+```bash
+cd ~cord/orchestration/xos-gui/generator-xos-gui-extension
+npm link
+```
+To run the generator, simply run `yo xos-gui-extension` from whatever location in your file system you wish to place your
+new GUI extension. The extension will prompt for a name for your extension.
+
+
+### Option 2: Copy over sample-gui-extension
+If you choose not to use the Yeoman generator, you can copy over the contents of `sample-gui-extension` to your desired
+destination in your file system. If you are creating a GUI extension to used with a service, we suggest creating the
+extension in a folder named `gui` in the service's `xos` folder as follows: `orchestration/xos_services/service_name/xos/gui/`.
+When changing the name of `sample-gui-extension`, you must be wary to change all instances of `sample-gui-extension` in the
+extension folder.
+
## Add an extension to a profile
-To deploy your GUI extension with a cord profile you'll need to reference it in `platform-install`.
+To deploy your GUI extension with a cord profile you'll need to reference it in `platform-install/profile-manifests`.
-Open the `profile-manifest` you're working on (eg: profile_manifests/frontend.yml) and locate `enabled_gui_extensions`.
+Open the `profile-manifest` you're working on (eg: `profile_manifests/ecord-global.yml`) and locate `enabled_gui_extensions`.
It may appear in two forms, depending whether or not there are others loaded extensions:
```yaml
enabled_gui_extensions:
@@ -28,7 +50,7 @@
```yaml
enabled_gui_extensions: []
```
-_NOTE: if it is not there, just create it_
+_NOTE: if it is not there, just create it._
To add your extension, just add it to the list:
```yaml
@@ -40,7 +62,7 @@
```
_NOTE: if it was defined as an empty array you'll need to remove the square brackets (`[]`)_
-The `name` field must match the directory in which the GUI Extension is built. You can update it in `conf/gulp.conf.js`, just locate:
+You must make sure that the `name` field matches the directory in which the GUI Extension is built. You can update it in `conf/gulp.conf.js`.
```js
exports.paths = {
src: 'src',
@@ -51,6 +73,97 @@
tasks: 'gulp_tasks'
};
```
-and replace `sample` with the appropriate name.
+and replace `sample` with your appropriate name. If you used the Yeoman generator, `sample` will already have been
+replaced with the GUI extension name you chose.
-The `path` field identify the directory (starting from the `repo` root), in which your extension is stored. As now is not supported the loading from external sources.
\ No newline at end of file
+The `path` field identifies the directory (starting from the CORD `repo` root), in which your extension is stored.
+Loading from external sources is not currently supported.
+
+## Additional Tips
+
+### Including Extra Files
+
+Additional necessary files (such as stylesheets or config files) can be added to the profile manifest as follows,
+with the extension's `src` folder as the root. Here, we use `xos-sample-gui-extension` as an example.
+
+```yaml
+enabled_gui_extensions:
+ - name: sample
+ path: orchestration/xos-sample-gui-extension
+ extra_files:
+ - app/style/style.css
+```
+
+### Generating config files
+
+During development, you may find it necessary to create separate config files in order to include other files used in
+your extension (such as images). The path to your extension may vary depending on whether you are running it locally
+(`./xos/extensions/extension-name`) vs. on a container in production (`./extensions/extension-name`).
+
+You can create separate `customconfig.local.js` and `customconfig.production.js` files in the `conf/` folder, and then edit the
+following portion of the appropriate `webpack.conf.js` file as follows:
+
+```js
+new CopyWebpackPlugin([
+ { from: `./conf/app/app.config.${env}.js`, to: `app.config.js` },
+ { from: `./conf/app/style.config.${brand}.js`, to: `style.config.js` },
+ // add your file here
+ { from: `./conf/app/customconfig.local.js`, to: `customconfig.js`}
+ ])
+```
+
+`webpack.conf.js` will be used in a local development environment, such as when running `npm start`
+
+`webpack-dist.conf.js` will be used in a production container after deploying a profile.
+
+### Handy XOS Components and Services
+
+The following XOS components and services may be helpful to you in your GUI extension development.
+
+#### XosComponentInjector
+Allows for the injection of components into the XOS GUI by specifying a target element ID. Useful IDs include:
+* `#dashboard-component-container`: the dashboard as seen on the XOS home
+* `#side-panel-container`: a side panel that can slide out from the right. However, there is also a `XosSidePanel`
+service that can make development easier.
+
+#### XosConfirm
+Allows for the creation of confirmation modal dialogs to confirm whether or not to execute a selected action.
+
+#### XosKeyboardShortcut
+Allows for the creation of custom user keyboard shortcuts. See the provided `components/demo.ts` as an example.
+
+#### XosModelStore
+Provides easy access to model ngResources provided by an XOS service. Can be used as follows in your component's
+associated TypeScript file:
+
+```typescript
+import {Subscription} from 'rxjs/Subscription';
+export class ExampleComponent {
+ static $inject = ['XosModelStore'];
+ public resource;
+ private modelSubscription : Subscription;
+ constructor(
+ private XosModelStore: any,
+ ){}
+
+ $onInit() {
+ this.modelSubscription = this.XosModelStore.query('SampleModel', '/sampleservice/SampleModels').subscribe(
+ res => {
+ this.resource = res;
+ }
+ );
+ }
+}
+export const exampleComponent : angular.IComponentOptions = {
+ template: require('./example.component.html'),
+ controllerAs: 'vm',
+ controller: ExampleComponent
+}
+```
+
+#### XosNavigationService
+Used to create custom navigation links in the left navigation panel.
+
+#### XosSidePanel
+Makes the injection of a custom side panel somewhat easier (no need to specify a target)
+
diff --git a/docs/developer/quickstart.md b/docs/developer/quickstart.md
index c0b05a0..a27c800 100644
--- a/docs/developer/quickstart.md
+++ b/docs/developer/quickstart.md
@@ -1,41 +1,42 @@
# GUI Quickstart
-_We assume that you already have the code downloaded with `repo`_
+_We assume that you already have the CORD source downloaded with `repo`_
-## Setting up the `frontend` configuration
+## Setting up a podconfig
-The easiest to work on the XOS GUI is to create a local deployment of the `frontend` configuration of CORD.
+For front-end development, using a podconfig with your choice of CORD flavors and the `mock` or `local` scenario is sufficient.
+`mock` will create a vagrant VM running all the necessary containers, while `local` directly creates the containers on your machine,
+so we recommend using `mock` when developing on your own laptop/desktop.
-You can find detailed instructions in the `platform-install` documentation, but for all that we need you can:
+Assuming you have already downloaded and ran the `cord-bootstrap.sh` script, setup is easy:
+
+```bash
+cd ~/cord/build/
+make PODCONFIG={CORD_FLAVOR}-mock.yml config
+make build |& tee ~/build.out
+```
+
+Further details on the mock configuration and its setup can be found at [Mock Configuration Workflow](/xos/dev/workflow_mock_single.md)
+
+### Login credentials
+After the mock profile finishes building, you can find the credentials necessary to login into XOS by running the following:
```
-cd opencord/build/platform-install
-vagrant up head-node
-vagrant ssh
-// you're now inside the vagrant VM
-cd cord/buid/platform-install
-ansible-playbook -i inventory/frontend deploy-xos-playbook.yml
-```
-
-
-> This commands will spin up a local vm and deploy the minimal configuration of CORD in it. <br/>
-> _NOTE the first you'll execute this commands they may take a long time (~20 min)_
-
-#### Retrieve the password to access XOS
-```
-cat ~/cord/buid/platform-install/credentials/xosadmin@opencord.org
+cat ~/cord/build/platform-install/credentials/xosadmin@opencord.org
// save the output somewhere
```
## Serving the GUI in development mode
-Once your basic CORD config is up and running you should be able to access the GUI at `http://192.168.46.100/xos/`
-but that's not your development copy, it is the one deployed inside a container in XOS.
+Once your basic CORD config is up and running you should be able to access the GUI at `http://192.168.46.100/xos/`.
+
+NOTE: This is not your development copy, it is the one deployed inside a container in XOS and will not change until
+the container is torn down and redeployed.
To launch a development copy:
```
// back to your local system
-cd opencord/orchestration/xos-gui
+cd cord/orchestration/xos-gui
npm install
npm start
```
@@ -45,9 +46,9 @@
**Now you're ready to start working on it!**
To get start, login using username `xosadmin@opencord.org` and the password you previously saved,
-then pick any file and make a change, you'll see the GUI reload.
+then pick any file and make a change, and you'll see the GUI reload.
-### Configuring the `dev` GUI
+## Configuring the `dev` GUI
There are two configuration file available in the application, and they depend on the environment. You can find the various possibilities in `conf/app`, and they regard application constants, such as `apiEndpoint`, or branding elements, such as `projectName`.
@@ -55,6 +56,8 @@
- `PROXY`: you can use this variable to send request to an arbitrary XOS installation (eg: `clnode022.clemson.cloudlab.us:8080`)
- `BRAND`: to configure style constants (eg: `cord`, `opencloud`)
+## Working with an existing XOS installation
+
You can also specify a different installation of XOS as backend by using the `PROXY` environment variable.
-For example you can connect to a remote CORD-in-a-box installation for debugging purposes with:
+For example, you can connect to a remote CORD-in-a-box installation for debugging purposes with:
`PROXY=ms1106.utah.cloudlab.us:8080 npm start`
diff --git a/src/app/core/services/navigation.spec.ts b/src/app/core/services/navigation.spec.ts
index bc27772..b9cb84c 100644
--- a/src/app/core/services/navigation.spec.ts
+++ b/src/app/core/services/navigation.spec.ts
@@ -116,7 +116,7 @@
service.add(testRoute);
service.add(testRoute);
expect($log.warn).toHaveBeenCalled();
- expect($log.warn).toHaveBeenCalledWith(`[XosNavigation] Route with label: ${testRoute.label}, state: ${testRoute.state} and parent: ${testRoute.parent} already exist`);
+ expect($log.warn).toHaveBeenCalledWith(`[XosNavigation] Route with label: ${testRoute.label}, state: ${testRoute.state} and parent: ${testRoute.parent} already exists`);
expect(service.query()).toEqual(defaultRoutes.concat([testRoute]));
});
});
diff --git a/src/app/core/services/navigation.ts b/src/app/core/services/navigation.ts
index e7fa4ed..660c144 100644
--- a/src/app/core/services/navigation.ts
+++ b/src/app/core/services/navigation.ts
@@ -71,24 +71,27 @@
return this.routes;
}
- add(route: IXosNavigationRoute) {
+ add(route: IXosNavigationRoute, override: boolean = false) {
if (angular.isDefined(route.state) && angular.isDefined(route.url)) {
throw new Error('[XosNavigation] You can\'t provide both state and url');
}
// NOTE factor this out in a separate method an eventually use recursion since we can nest more routes
+ let preExisting = null;
const routeExist = _.findIndex(this.routes, (r: IXosNavigationRoute) => {
- if (r.label === route.label && r.state === route.state && r.parent === route.parent) {
+ if (r.label === route.label && (r.state === route.state || override) && r.parent === route.parent) {
+ preExisting = r;
return true;
}
else if (_.findIndex(r.children, route) > -1) {
+ preExisting = r;
return true;
}
return false;
}) > -1;
- if (routeExist) {
- this.$log.warn(`[XosNavigation] Route with label: ${route.label}, state: ${route.state} and parent: ${route.parent} already exist`);
+ if (routeExist && !override) {
+ this.$log.warn(`[XosNavigation] Route with label: ${route.label}, state: ${route.state} and parent: ${route.parent} already exists`);
return;
}
@@ -97,6 +100,9 @@
const parentRoute = _.find(this.routes, {state: route.parent});
if (angular.isDefined(parentRoute)) {
if (angular.isArray(parentRoute.children)) {
+ if (override) {
+ _.remove(parentRoute.children, r => r === preExisting);
+ }
parentRoute.children.push(route);
}
else {
@@ -104,11 +110,14 @@
}
}
else {
- this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exists`);
+ this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exist`);
return;
}
}
else {
+ if (override) {
+ _.remove(this.routes, r => r === preExisting);
+ }
this.routes.push(route);
}
}