Overview
This Web SDK enables you
- to develop web applications that can be deployed to the platform,
- to communicate authenticated with our API,
- to apply default or branded UI components to your custom application.
The Web SDK consists of the following packages deployed to npm in the scope @c8y
:
These packages depend on each other from top to bottom. While the @c8y/client
is a very low-level API interface with nearly no dependencies, the @c8y/apps
provide feature rich applications by including @c8y/ngx-components
and @c8y/client
.
The goal of these splittings is to provide the right package for every use case, e.g. if you want to build a small application with React you could use the @c8y/client
to do the API interaction. If you need a brandable feature rich application which is close to our Cockpit or Device Management application you could use @c8y/ngx-components
together with @c8y/stlyes
.
Following is a list which explains the use cases of each package.
- @c8y/client: Use this client to access our API. The client is isomorphic, that means it could be used in node.js and in the browser.
- @c8y/ngx-components: A components collection and data access layer for Angular applications. This package can be used to build Angular applications.
- @c8y/styles: The styles for the look & feel of an application. Extend this package to apply a custom branding to your application.
- @c8y/apps: Example and bootstrapping applications to easily let you start with the Web SDK.
You can find all our packages on npm. To quickly get you bootstrapped with these packages we have built an CLI tool called @c8y/cli
. Next, we will showcase how to get started with the @c8y/cli
command line tool.
Getting started
First install the Command Line Interface (CLI) which helps you with bootstrapping an application:
$ npm install -g @c8y/cli
Next, bootstrap a new blank application with the new
command:
$ c8ycli new myApp
Info: When you want to see the possibilities and implementation details of the Web SDK you should try the tutorial application. You can install it by running
c8ycli new <<your-app-name>> tutorial
.
Once the CLI installation is completed change to the newly created folder and run npm install:
$ cd myapp
$ npm install
After all packages are installed you can start the application by running:
$ npm start
If you point your browser to http://localhost:9000/apps/myapp/ you will get a login screen which proxies to the tenant defined in the start script. If you cannot log in, it might be pointing to the wrong instance. To change the proxy to your tenant URL change the start
script in the script section of the newly created package.json:
{
"start": "c8ycli server -u http://your-tenant.my-provider.com"
}
After logging in you should see a barely empty starter application. If you want to start with a more complex example read the documentation about @c8y/apps. If you want to build and deploy your application read more about the necessary commands of the developer command line tool.
Info: If you want to extend an existing application like Cockpit you can spin up a hybrid application. This allows you to combine existing AngularJS plugins into the new Web SDK, see Migration.
Info: You need to provide your tenant name or the tenant ID on login (as the application cannot derive it from the URL on localhost). If you don’t know your tenant name or the tenant ID you can use the REST API to request it.
First route & component
After creating the empty bootstrapping application you might want to start with your first content. To do so, add a new component to your project and save it as hello.component.ts
:
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: `
<c8y-title>Hello World</c8y-title>
<p>My first content.</p>
`
})
export class HelloComponent {
constructor() {}
}
To hook the new component into the application you need to declare the new component and add it to a route in the app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { CoreModule, BootstrapComponent, CommonModule} from '@c8y/ngx-components';
import { HelloComponent } from './hello.component'; // don't forget to import the new component
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot([{ path: '', component: HelloComponent }], // hook the route here
{ enableTracing: false, useHash: true }),
CoreModule,
CommonModule
],
declarations: [HelloComponent], // add deceleration here
bootstrap: [BootstrapComponent]
})
export class AppModule {}
If you start this application and log in you will see an application similar to the following screenshot. To extend this application you can use the Angular Router in combination with our @c8y/ngx-components.
Applications options
Each UI application can be customized with a set of defined options. The options objects are defined in JSON and read and merged at runtime with the following order:
- Static options defined at build time
- Dynamic fetched options on application boot
- URL options that can be added by the user as URL query params (mostly for debugging purposes)
This process of collecting and merging the different options is executed by a thin bootstrap layer included in @c8y/cli
.
Although all of the options can be defined on any of the 3 levels some might not make sense at runtime, as they just influence the build process, or require a complex object making it particularly tricky to write as a URL query parameter.
Static options
Defined in the package.json
of the application inside the fragment c8y.application
:
{
"c8y": {
"application": {
"name": "cockpit",
"contextPath": "cockpit",
"key": "cockpit-application-key",
"tabsHorizontal": true,
"upgrade": true,
"rightDrawer": true,
"contentSecurityPolicy": "default-src 'self' 'unsafe-inline' http: https: ws: wss:; script-src 'self' *.mapquestapi.com 'unsafe-inline' 'unsafe-eval' data:; style-src * 'unsafe-inline' blob:; img-src * data:; font-src * data:; frame-src *;"
}
}
}
Can also be passed to c8ycli
:
c8ycli build --app.contextPath=cockpit2 --app.dynamicOptionsUrl="/apps/public/public-options/options.json"
Dynamic fetched options
Using the static options dynamicOptionsUrl
the application will try to load a json from the specified URL at boot time. In the platform’s built-in applications this option is set to /apps/public/public-options/options.json
as that mechanism to provide instance level and enterprise tenant customization.
As this property is defined statically at build time, it is possible for the application developer to decide if and where from their applications should load the dynamic fetched options at runtime.
URL options
These can just be appended to the URL of the application as query parameters.
https://<instance domain>/apps/cockpit?dynamicOptionsUrl=/apps/my-options/options.json&rightDrawer:false
Options
Here is a list of the built-in options. As in the end this is just a plain old javascript object this list can be easily extended with any property a developer might want to include in his applications or extensions.
export class ApplicationOptions {
name: string; // To be saved to the server
contextPath: string; // To be saved to the server
key: string; // To be saved to the server
upgrade?: boolean; // true if the application is hybrid using Angular and angularJS simultaneously
brandingEntry?: string; // the entry path to the branding
tsConfigPath?: string; // path to tsCconfig if typescript is used, defaults to ./tsconfig.json
entryModule?: string;
indexTemplate?: string; // path to the index.html to be used, otherwise the default will be used
dynamicOptionsUrl?: string; // URL to load the dynamic fetched options
faviconUrl?: string; // URL for the favicon
brandingUrl?: string; // URL for a CSS that will replace the default branding
brandingCssVars?: { // Object with properties that will be converted to CSS custom properties
[key: string]: string
};
languages?: { // Object with properties to add and change the languages available in the applications
[langCode: string]: {
name: string;
nativeName: string;
url: string;
}
};
localePath?: string; // The folder where the translations po files are loaded from
extraCssUrls?: string[]; // URLs for extra CSS files to be loaded at runtime
docs?: {
noDefault: boolean, // Hide default links to documentation
excludeDefault: string[], // The list of regex strings to be matched with the default docs url
links: Array<{ // Additional documentation links to be displayed
icon: string;
label: string;
url: string;
}>
};
noAppSwitcher?: boolean; // Hides the application from the application switched (saved to the server)
globalTitle?: string; // HTML page title
hidePowered?: boolean; // Hide powered by at the bottom of the navigator
supportUrl?: boolean | string; // URL for support link
supportUserString?: string;
rightDrawer?: boolean; // Show or hide the right drawer
hideNavigator?: boolean; // Show or hide the navigator
tabsHorizontal?: boolean; // Show tabs horizontally or vertically
loginExtraLink?: { // Extra link to add to login screen
url: string,
label: string
};
storageLimitationFeatureEnabled?: boolean;
companyName?: string; // Company name used to prompt the user about support staff
guideHrefTemplate?: string; // The full URL for documentation, by default it's ${docsBaseUrl}${partialUrl}
docsBaseUrl?: string; // The base URL for documentation
contentSecurityPolicy?: string; // CSP string added to the index.html
imports?: string[]; // legacy plugin imports
}
Developer command line tool
To support you with bootstrapping, running and deploying applications we have build a Command Line Interface. The tool is the successor of the cumulocity-node-tools
. To avoid conflicts, it listens to the new command c8ycli
instead of c8y
. You can install it via npm:
npm install -g @c8y/cli
Usage
c8ycli [options] [command]
Options
-u, --url <url> The URL of the remote instance
--version Provides version number
-h, --help Provides usage information
Commands
All the commands except of new
take an array of glob patterns. These will be solved to folders or entry point manifests.
new [name] [template] Creates a folder to start a new application or extend an existing one
serve [options] [appPaths...] Runs local development server
build [options] [appPaths...] Builds the specified applications
deploy [options] [appPaths...] Deploys applications from the specified paths
locale-extract [options] [srcPaths...] Extracts all strings for translation and outputs the .po files to defined folder
The new
command
The c8ycli new [name] [template]
helps to start an empty application or to extend one of our existing applications (Cockpit, Devicemanagement or Administration). To extend an existing application use as [name]
and [template]
the name of the existing application like this:
$ c8ycli new cockpit cockpit
Application options
Application options can be defined with --app.<option>=<value>
. These will be applied to all applications found with [appPaths...]
.
--app.name="My Application"
--app.key=myapp-key
--app.contextPath=myapplication
--app.brandingEntry="./branding/mybranding.less"
Webpack options
Webpack options can be defined with --env.<option>=<value>
. These will be directly passed to the webpack configuration.
--env.mode="production"
--env.hmr
Angular CLI
When developing a pure Angular you can create an Angular CLI project and add Cumulocity CLI to it. This functionality is only available after 1004.2.0.
Install Angular CLI
Follow the instructions to install @c8y/cli globally.
npm install -g @angular/cli
Create a new project
ng new my-first-iot-project
cd my-first-iot-project
Add Cumulocity CLI
cd my-first-iot-project
ng add @c8y/cli
Run application
ng serve
In your browser, open http://localhost:4200/ to see the new application run.
You can configure the application options inside the package.json file and customize branding with LESS or CSS custom variables
Branding
For styling the application global CSS created with LESS is used. These styles are based on Bootstrap 3, and the original LESS source is distributed via the npm package @c8y/style. By extending these styles it is possible to change any detail of the application but the vast majority of developer want to change: colors, logos and fonts and these can be very easily achieved by replacing a few variables.
To override the variables it is possible to use:
- LESS variables at build time
- Custom CSS properties (at build time or configurable at runtime)
CSS custom properties
Exposed via CSS custom properties there is only a subset of the LESS variables available. Here is a list of the available variables.
:root {
--brand-primary: gold ;
--brand-complementary: darkgreen;
--brand-dark: red;
--brand-light: purple;
--gray-text: #333;
--link-color: var(--brand-primary);
--link-hover-color: var(--brand-complementary);
--body-background-color:#f2f3f4;
--brand-logo-img: url('/apps/ui-assets-management/logo-nav.svg');
--brand-logo-img-height: 20%;
--navigator-platform-logo: url('/apps/ui-assets-management/logo-nav.svg');
--navigator-platform-logo-height: 36px; /* height of the logo set to 0 to hide the element */
--navigator-font-family: inherit;
--navigator-app-name-size: 16px; /* font size of the application name set to 0 to hide app's name */
--navigator-app-icon-size: 46px; /* size of the application icon. set to 0 to hide the application icon.*/
--navigator-bg-color: var(--brand-primary);
--navigator-header-bg: var(--navigator-bg-color);
--navigator-text-color: #ffffff;
--navigator-separator-color: rgba(0,0,0,.05);
--navigator-active-color: var(--navigator-text-color);
--navigator-active-bg: var(--brand-complementary);
--header-color: #ffffff;
--header-text-color: var(--brand-dark);
--header-hover-color:var(--brand-primary);
--header-border-color: rgba(57,72,82,.05);
--font-family-base: "Roboto", Helvetica, Arial, sans-serif;
--headings-font-family: var(--font-family-base);
}
Note that these can be customized at runtime using application options using the property brandingCssVars
.
The option is only available after version 9.22.0.
Using LESS
Prerequisites
If you do not use the @c8y/cli make sure that you install the base styles from npm with:
npm install @c8y/style
- Create a LESS file called for instance
branding.less
. - Save it inside a new folder, which can have any name you like.
- Inside this folder, create a sub folder for images.
my-application
│ app.modules.ts
│ index.ts
│ packages.json
| ...
└───branding
│ │ branding.less
│ └───img
│ │ favicon.ico
│ │ main-logo.svg
│ │ tenant-brand.svg
│
The first line of code within the branding.less
has to be:
@import '~@c8y/style/extend.less';
Example customizations
At this point we are able to change the desired variables according to our needs.
Let us change for example the most important color of your branding, the main color, called brand-color.
This is done by setting the respective LESS variable to a new color.
@brand-color: red;
User interface elements like buttons, active navigation nodes or even active tabs as well as also hover-states of buttons are red now.
What about changing the main logo that is located at the top of the login dialog? Look at this:
@{logo-login} { background-image: url('./img/logo-main.svg')}
@brand-logo-height: 48%;
You can check the branding changes with the help of the @c8y/cli.
c8ycli server --app.brandingEntry="<path-to-your-branding.less>"
You can also take a look at our tutorial application which has an example branding applied:
c8ycli new <appName> tutorial
More branding details
There are three main areas of a branding that you can easily control.
Colors
The colors that may be edited are separated in multiple categories, like:
- brand colors
- status colors
- gray shades
- component colors
Brand colors
@brand-color: #53cd61;
@brand-primary: @brand-color;
@brand-complementary: #a8b3b5;
@brand-primary-light: lighten(@brand-primary, 20%);
Status colors
@brand-success: #5cb85c;
@brand-info: @brand-color;
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
@danger: #D90000;
@warning: #FDC000;
@dark-warning: #FF8000;
@success: #5cb85c;
Gray shades
@gray-text: #444;
@gray-darker: #2c3637;
@gray-dark: #3b4748;
@gray-medium-dark: #49595B;
@gray-medium: #6D7A7C;
@gray: #8A9596;
@gray-light: #cacece;
@gray-lighter: #f8f8f8;
@gray-white: #fcfcfc;
@text-muted: @gray;
Component colors
Two components are always visible to the user, the header and the navigator. Therefore you should determine the look & feel of these components with care.
/* HEADER */
@headerColor: white;
@header-text-color: @gray-medium-dark;
@header-text-color-hover: @brand-primary;
@header-active-color: darken(@gray-medium-dark, 15%);
/* NAVIGATOR */
@navColor: @gray-darker;
@navColorHeader: transparent;
@navigator-title-color: white;
@navigator-text-color: @gray-lighter;
@navigator-separator-color: fade(white, 5%);
@navigator-font-family: @headings-font-family;
@navigator-font-size: 13px;
@navigator-active-color: white;
@navigator-active-bg: @brand-primary;
As you can see, some variables re-use others. Be careful that these variables are all defined to avoid build errors.
Logos
There is no branding without logos.
You can change the logo at the top of the login dialog, the tenant brand logo and of course the favicon.
To change the favicon, enter:
// to be loaded by webpack
.favicon-webpack-loader { background: url('./img/favicon.ico') }
To change the main logo, enter:
@{logo-login} { background-image: url('./img/main-logo.svg') }
@brand-logo-height: 48%;
To change the tenant brand logo inside the navigator, enter:
@{logo-navigator} { background-image: url('./img/tenant-brand.svg') }
@navigator-platform-logo-height: 100px;
Typography
The look and feel of an application is also driven by its typography. Of course you can change the font as well.
@font-family-sans-serif: "Lato",Arial, Verdana, sans-serif;
@font-family-base: @font-family-sans-serif; @headings-font-family: "Roboto",Arial, Verdana, sans-serif;
Example Branding
Above we described the possible options for creating your custom branding in detail. If you do not want to start from scratch in every application use the following example branding as snippet. It defines the most important variables.
@import '~@c8y/style/extend.less';
// Replace and uncomment each variable as you need them
/* LOGOS */
.favicon-webpack-loader { background: url('./img/favicon.ico') } // to be loaded by webpack
@{logo-login} { background-image: url('./img/logo-main.svg') }
@brand-logo-height: 48%; // percentage - height / width * 100
@{logo-navigator} { background-image: url('./img/logo.svg') }
@navigator-platform-logo-height: 100px;
/* COLORS */
@brand-color: #53cd61; // main color
@brand-primary: @brand-color;
@brand-complementary: #a8b3b5;
@brand-primary-light: lighten(@brand-primary, 20%);
// status colors
@brand-success: #5cb85c;
@brand-info: @brand-color;
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
@danger: #D90000;
@warning: #FDC000;
@dark-warning: #FF8000;
@success: #5cb85c;
// grays
@gray-text: #444;
@gray-darker: #2c3637;
@gray-dark: #3b4748;
@gray-medium-dark: #49595B;
@gray-medium: #6D7A7C;
@gray: #8A9596;
@gray-light: #cacece;
@gray-lighter: #f8f8f8;
@gray-white: #fcfcfc;
@text-muted: @gray;
@body-background-color: #f8f8f8; // page background color - always use a light background
/* HEADER */
@headerColor: white;
@header-text-color: @gray-medium-dark;
@header-text-color-hover: @brand-primary;
@header-active-color: darken(@gray-medium-dark, 15%);
/* NAVIGATOR */
@navColor: @gray-darker;
@navColorHeader: transparent;
@navigator-title-color: white;
@navigator-text-color: @gray-lighter;
@navigator-separator-color: fade(white, 5%);
@navigator-font-family: @headings-font-family;
@navigator-font-size: 13px;
@navigator-active-color: white;
@navigator-active-bg: @brand-primary;
// when set adds a vertical gradient in the navigator background
// @grad-top: "";
// @grad-bottom: "";
/* TYPOGRAPHY */
// @font-family-sans-serif: "Lato",Arial, Verdana, sans-serif;
// @font-family-base: @font-family-sans-serif;
// @headings-font-family: "Roboto",Arial, Verdana, sans-serif;
/* BUTTONS */
// @btn-border-radius-base: 2px;
// @btn-border-radius-large: @btn-border-radius-base;
// @btn-border-radius-small: @btn-border-radius-base;
// @btn-shadow: none;
/* COMPONENTS */
// @spinner-color: lighten(@brand-primary, 30%);
// @link-color: #337ab7;
// @link-hover-color: darken(@link-color, 15%);
// @input-focus-color: #66afe9;
// @body-background-pattern: "";
// @darker-header: @gray-dark;
// @appswitcher-background: none;
// @table-bg-hover: fade(black, 1.5%);
// @header-app-name: @header-text-color;
// @image-path: 'img/';
ngx-components
ngx-components is a components collection and data access layer for Angular applications. It allows to access our platform from within an Angular application as well as to provide the core components. To achieve this the ngx-components consists of two basic imports:
- core (
@c8y/ngx-components
) which contains all core components like title, navigator or tabs. - api (
@c8y/ngx-components/api
) which enables dependency injection of the @c8y/client services.
The full documentation of all modules and components can be found here
Prerequisites
If you do not use the @c8y/cli to bootstrap a new application you first need to install the package:
$ npm install @c8y/ngx-components
Next, you can add the ngx-components modules to your app module (e.g. app.module.ts):
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { CoreModule, BootstrapComponent} from '@c8y/ngx-components';
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot([], { enableTracing: false, useHash: true }), // 1
CoreModule // 2
],
bootstrap: [BootstrapComponent] // 3
})
export class AppModule {}
- Make sure to set
useHash
navigation to true as the platform does not support pushState - Import the
CoreModule
to allow the use of thec8y-
prefixed components. - Bootstrap your application with the
BootstrapComponent
which will use the<c8y-bootstrap>
component to initialize the root application. Alternatively, you can bootstrap a component of your choice and include that tag into its template or only reuse the given components.
Extension points
To extend and compose an application, ngx-components provide four core architecture concepts called Extensions points:
-
Content Projection (CP):
This concept allows to project content from one component to another. For example, you can configure the title of a page by setting a<c8y-title>
in any other component. The content of the<c8y-title>
tag is then projected to an outlet component, which is placed in the header bar. The benefit of this concept is that you can place anything into the projected content, for example you can project another custom component into the title.
A good example to use this concept is thec8y-action-bar-item
which uses arouterLink
directive from Angular to route to a different context:<c8y-action-bar-item [placement]="'right'"> <a class="btn btn-link" routerLink="add"> <i class="fa fa-plus-square"></i> {{'Add' | translate}} </a> </c8y-action-bar-item>
The above example gives you an action bar item in the header bar, regardless in which component you define it. If the component is initialized the item is shown and it is removed on destroy.
-
Multi Provider (MP):
The Multi Provider extension allows a declarative approach to extend the application. Instead of defining it in the template, you extend an already defined factory via aHOOK
. This hook gets executed if the application state changes. The return values are then injected into the page. You can use the normal dependency injection system of Angular and as a result you can usually return an Observable, Promise or Array of a certain type. As an example we can define the tabs of certain routes by hooking into theHOOK_TABS
provider:import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Tab, TabFactory, _ } from '@c8y/ngx-components'; @Injectable() export class ExampleTabFactory implements TabFactory { // 1 constructor(public router: Router) { } get() { const tabs: Tab[] = []; if (this.router.url.match(/world/g)) { // 2 tabs.push({ path: 'world/awesome', label: 'Awesome', icon: 'angellist' } as Tab); } return tabs; // 3 } }
By defining a
Injectable()
services which implements theTabFactory
(1) you can define which tabs you want to show on which page. By using theRouter
service of Angular we check in this example if the URL of the route contains the name world (2) and only if this matches the tab labeledAwesome
is returned (3). By hooking this into your provider definition of your module you make sure, that theget()
function is checked on each route change:@NgModule({ declarations: [ /* ... */ ], imports: [ BrowserModule, RouterModule.forRoot([/* ... */], { enableTracing: false, useHash: true }), CoreModule, CommonModule ], providers: [ { provide: HOOK_TABS, useClass: ExampleTabFactory, multi: true} // hook the ExampleTabFactory defined earlier ], bootstrap: [BootstrapComponent] }) export class AppModule { }
Usually you use Content Projection within a route and Multi Provider if the context is shared across multiple routes or needs more complex logic to resolve the content. Examples: a title is just valid for one route -> use Content Projection. A tab should only be shown on specific routes under certain conditions -> use Multi Provider. The following hooks are currently supported:
HOOK_TABS
: Allows to show tabs on certain conditions.HOOK_NAVIGATOR_NODES
: Enables navigator nodes to be shown.HOOK_ACTION
: Enables to define the global actions which should be shown or enabled on certain conditions.HOOK_BREADCRUMB
: Can be used to show breadcrumbs in the header bar.HOOK_SEARCH
: Allows to define the search to be shown or not.
-
Services
A service is defined for most components of ngx-components. They can be used via the dependency injection concept of Angular, that means that these services can be injected in the constructor of a component and then add or remove certain UI elements. The following example shows how to use that concept with an alert:constructor(private alert: AlertService) { try { // do something that might throw an exception } catch(ex) { this.alert.add({ text: 'Something bad happened!' type: 'danger'; detailedData: ex; } as Alert); } }
-
Legacy plugins
If you are extending a default application (Cockpit, Device Management or Administration) you get a file calledng1.ts
. These are so called plugins which haven’t been migrated to Angular yet and are still using angular.js. You can add or remove these plugins to customize the application appearance like it has been done previously in a target file by theaddImports: []
orremoveImports: []
property. The following shows an example which removes the default import in the angular.js target file:{ "name": "example", "applications": [ { "contextPath": "cockpit", "addImports": [ "my-plugin/cockpit-home", ], "removeImports": [ "core/cockpit-home" ] } ] }
You get the same result in the new Angular framework by modifying the
ng1.ts
file of the cockpit app:import '@c8y/ng1-modules/core'; // [...] more imports removed for readability import '@c8y/ng1-modules/alarmAssets/cumulocity.json'; // import '@c8y/ng1-modules/cockpit-home/cumulocity.json'; // 1 import '@c8y/ng1-modules/deviceControlMessage/cumulocity.json'; import '@c8y/ng1-modules/deviceControlRelay/cumulocity.json'; // [...] more imports removed for readability import 'my-plugin/cumulocity.json'; // 2
As you can see we simply removed the import of the original welcome screen plugin (1.) and replaced it by the custom implementation (2.). Note that all angular.js plugins need to have the
/cumulocity.json
addition to tell webpack that a legacy plugin is imported.To use legacy plugins in your custom non-default application you need to set the
upgrade
flag in the package.json file and use the same import approach like described before:"c8y": { "application": { "name": "myapp", "contextPath": "myapp", "key": "myapp-application-key", "upgrade": true } }
Also the module definition of your application must be changed to support these plugins:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { UpgradeModule as NgUpgradeModule } from '@angular/upgrade/static'; import { CoreModule } from '@c8y/ngx-components'; import { UpgradeModule, HybridAppModule } from '@c8y/ngx-components/upgrade'; import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator'; @NgModule({ imports: [ BrowserModule, RouterModule.forRoot([], { enableTracing: false, useHash: true }), CoreModule, AssetsNavigatorModule, NgUpgradeModule, // Upgrade module must be the last UpgradeModule ] }) export class AppModule extends HybridAppModule { constructor(protected upgrade: NgUpgradeModule) { super(); } }
That will let your app start in a hybrid mode, which allows to use angular.js and Angular plugins/modules.
To determine which extension points are supported and which concept should be used for certain scenarios the following section gives an overview on all supported components and explains in which case they should be used.
Data access to the platform
The CommonModule
exports the DataModule
, an abstraction of the @c8y/client which allows to use the services of the client with the dependency injection system of Angular. So in any module in which the CommonModule
or DataModule
is imported you can use simple injection to access data of the platform:
import { Component } from '@angular/core';
import { AlarmService } from '@c8y/client'; // 1
@Component({selector: 'app-alerts', template: ''})
export class AlarmComponent {
constructor(private alarmService: AlarmService) {} // 2
async getAllAlarms() {
const alarms = await this.alarmService.list(); // 3
return alarms.data;
}
}
- Import the service from the @c8y/client package.
- Dependency inject that service.
- Use that service to request data from the platform.
For detailed information on all available services and on how to filter and select data refer to @c8y/client.
Client library
The @c8y/client is an isomorphic (node and browser) Javascript client library for the Cumulocity IoT platform API.
Installation
npm install @c8y/client
Usage
Use client.<endpoint>.list()
to request listed data from the Cumulocity REST API and
client.<endpoint>.detail(<id>)
to request detail information. These methods always return a promise. To get an observable use list$
or detail$
.
In the following sections, the default signature of these functions is described. For detailed information, refer to the complete documentation).
Get detail and list data with promises (pull)
Method | Description | Parameters | Return |
---|---|---|---|
detail(entityOrId) |
Request detail data of a specific entity. | entityOrId: string | number | IIdentified : An object which contains an id or an id as number or string. |
Promise<IResult<TData>> : The list as Promise wrapped in an IResult. IResultList contains data and response. |
list(filter) |
Request a list of data with an optional filter. | filter:object : (optional) A filter for paging or filtering of the list. |
Promise<IResultList<TData>> : The list as Promise wrapped in an IResultList. IResultList contains data, response and paging. |
-
Example for receiving details of one managedObject of the inventory via
detail
:const managedObjId: number = 1; (async () => { const {data, res} = await client.inventory.detail(managedObjId); })();
-
Example for receiving a list of one managedObject of the inventory via
list
:const filter: object = { pageSize: 100, withTotalPages: true }; (async () => { const {data, res, paging} = await client.inventory.list(filter); })();
Accessing a microservice with the Fetch API
The client internally uses the Fetch API. By accessing this core function, you can do any authenticated request to any resource. Standalone you can use core.client.fetch(url, options)
and in @c8y/ngx-components/data
for Angular you simply need to inject the FetchClient
:
constructor(private fetchClient: FetchClient) {} // di
async getData() {
const options: IFetchOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
};
const response = await fetchClient.fetch('/service/my-service', options); // Fetch API Response
}
All fetch responses can be parsed to JSON if the content type is set correctly. Find more information on handling fetch responses in the MDN documentation.
Authentication strategy
In the Cumulocity platform we currently allow two ways to authenticate:
- Basic Auth: The authentication header is injected into each request.
- Oauth: The client doesn’t know about the authentication header. The header is set in a cookie.
To quickly get you started, the @c8y/client provides a shorthand static function which always uses Basic Auth and verifies the login directly:
await Client.authenticate({ tenant, user, password }), url);
It internally creates a client instance and tries to contact the API to verify if the given credentials are correct. In some cases you need to use a more fine-grained authentication, e.g. when you don’t know which authentication strategy the user is going to use. In this case you need to construct an own instance of the client and pass the authentication strategy to it:
const baseUrl = 'https://acme.cumulocity.com';
const client = new Client(new CookieAuth(), baseUrl); // use here `new BasicAuth()` to switch to Basic Auth
try {
const { data, paging, res } = await client.user.currentUser();
console.log('Login with cookie successful');
} catch(ex) {
console.log('Login failed: ', ex)
}
Subscribe to detail and list data with observables (push)
The detail$
and list$
functions allow to subscribe to realtime channels that omit data on each change:
Method | Description | Parameters | Return |
---|---|---|---|
detail$(entityOrId, options) |
Returns an observable for detail data of one entity | entityOrId: string | number | IIdentified : An object which contains an id or an id as number or string.options: IObservableOptions : (optional) An configuration object to define the observable. |
Observable<TData>> : The list as subscribable observable. |
list$(filter, options) |
Returns an observable for a list of entities. | filter: object : (optional) A filter for paging or filtering of the list (optional).options: IObservableOptions : (optional) An configuration object to define the observable. |
ObservableList<TData>> : The list as subscribable observable. |
-
Example for receiving details of one managedObject of the inventory via
detail$
:const managedObjId: number = 1; const detail$ = client.inventory.detail$(managedObjId); detail$.subscribe((data) => console.log(data));
-
Example for receiving a list of one managedObject of the inventory via
list$
:const list$ = client.inventory.list$(); list$.subscribe((data) => console.log(data));
Observables can be configured by adding an
IObservableOptions
object with these default properties:{ hot: true, // true = shares one network request realtime: false, // true = listen to real-time changes pagingStrategy: PagingStrategy.PROGRESSIVE, // ALL = All pages are loaded // NONE = only current page is loaded // PROGRESSIVE = load pages with more() realtimeAction: RealtimeAction.FULL, // FULL = use all CRUD realtime actions pagingDelay: 0 // Delay the next page load by x ms realtimeFilter: undefined // A optional additional filter }
Examples
Below some examples are provided which may help you to get started. To see a complex and full implementation of the client into Angular, have a look at @c8y/cli and the new
command to spin up a example application for Angular.
Requesting list data from the inventory:
import { Client } from '@c8y/client';
const baseUrl = 'https://demos.cumulocity.com/';
const tenant = 'demos';
const user = 'user';
const password = 'pw';
(async () => {
const client = await Client.authenticate({
tenant,
user,
password
}, baseUrl);
const { data, paging } = await client.inventory.list();
// data = first page of inventory
const nextPage = await paging.next();
// nextPage.data = second page of inventory
})();
Getting an observable of the inventory endpoint:
import { Client } from '@c8y/client';
const baseUrl = 'https://demos.cumulocity.com/';
const tenant = 'demos';
const user = 'user';
const password = 'pw';
(async () => {
const client = await Client.authenticate({
tenant,
user,
password
}, baseUrl);
client.inventory.list$().subscribe((data) => {
// request inventory data via fetch and adds realtime if data changes
console.log(data);
});
})();
Using realtime:
// realtime event
const subscription = client.realtime.subscribe('/alarms/*', (data) => {
console.log(data); // logs all alarm CRUD changes
});
client.realtime.unsubscribe(subscription);
// realtime observable
const observable$ = client.realtime.observable('/alarms/*');
const observableSubscription = observable$.subscribe((data) => {
console.log(data)); // logs all alarm CRUD changes
});
observableSubscription.unsubscribe();
Authenticate in node.js
The constructor new Client([...])
initializes a new client which allows to request data from the API. Unlike to Client.authenticate([...])
it needs a tenant given and does not verify if the login is correct. This is useful if you are developing a node.js microservice.
const auth = new BasicAuth({
user: 'youruser',
password: 'yourpassword',
tenant: 'acme'
});
const baseUrl = 'https://acme.cumulocity.com';
const client = new Client(auth, baseUrl);
(async () => {
const { data, paging, res }); = await client.inventory.list({ pageSize: 100 });
})();
Migrating
This section targets to developers who have either already developed a plugin with the AngularJS SDK for plugins or want to extend the existing default applications.
You should have read and understand the following concepts:
- Overview, explaining when to use which SDK.
- Upgrading to Angular, explaining since which version you can use the new SDK.
- Developer command line tool, enabling you to install the new tooling.
Setting up a hybrid application
With a hybrid application AngularJS and Angular can be used at the same time. It allows to use not-migrated plugins written in AngularJS in an Angular context. The CLI automatically sets up a hybrid application, if you use one of our default applications as a template (cockpit, devicemangement or administration). The command to be used is c8ycli new <your-app-name> <template-name>
.
For example, to override the default cockpit you use:
c8ycli new cockpit cockpit
When you run this command it provides you with a very simple file structure that contains the following files:
app.module.ts
: An Angular entry module in which you can hook Angular routes. Note that for a Cumulocity application you don’t get access to the root element and therefore there is no root template (index.html) which you can modify.package.json
: The package.json describes the dependencies and the application itself. Thec8y.upgrade: true
flag tells our webpack plugins to allow AngularJS plugins.ng1.ts
: Our default AngularJS plugins for the Cockpit application.index.ts
: The bootstrapping which is setup to bootstrap a hybrid application.polyfills.ts
: Polyfills setup to run in IE11.tsconfig.json
: The typescript configuration.
Importing AngularJS plugins
If you want to integrate your custom plugin into an application, you first need to set up the hybrid application for the application into which you want to import the plugin. Then you simply copy the plugin to the hybrid applicatiob and reference the cumulocity.json
of the plugin in the ng1.ts
file with an import:
import 'my-custom-plugin/cumulocity.json';
Webpack now reads the manifest file and converts the content to CommonJS require imports so that the plugin will be loaded and applied to the application.
Custom bootstrapping and upgrading
In the app.module.ts
file we bootstrap the hybrid application with the following code on the HybridAppModule
.
import { UpgradeModule as NgUpgradeModule } from '@angular/upgrade/static';
import { ng1Modules } from './ng1';
export abstract class HybridAppModule {
ng1Modules = ng1Modules;
protected upgrade: NgUpgradeModule;
ngDoBootstrap() {
(window as any).bootstrap();
this.upgrade.bootstrap(document.getElementById('app'), this.ng1Modules, { strictDi: false });
}
}
This module not only bootstraps the upgrade module but also initializes the Angular application.
In the app.module.ts
you can control the bootstrapping. For example, you can add another module to the bootstrapped ng1 modules:
constructor() {
this.ng1Modules.push('my-custom-module');
}
This will also load your my-custom-module
. You can use this to override the ngDoBootstrap()
function to whatever fits your application needs, e.g. upgrade or downgrade Angular services as shown in the Angular docs.
Applications
The application package (@c8y/apps
) provides example applications for the Web SDK.
Prerequisites
To use the @c8y/apps you need to install the @c8y/cli. Refer to its documentation for installation instructions.
Once installed you can run:
$ c8ycli new [your-app-name] [example-name]
For example, to generate the tutorial application with the name my-app
you need to run:
$ c8ycli new my-app tutorial
Included applications
The following table provides an overview on the currently supported applications:
Name | Description |
---|---|
application |
An empty application to quickly bootstrap new applications. It is the default application and used if you don’t specify an [example-name] . |
tutorial |
An application that already assembles most of the concepts of the @c8y/ngx-components. Use this to get real code examples. |
cockpit |
The Cockpit default application. Use this to extend the existing Cockpit application. |
devicemanagement |
The Devicemanagement default application. Use this to extend the existing Device Management application. |
administration |
The Administration default application. Use this to extend the existing Administration application. |
Branding and language customization
Using application options, each tenant can customize the look and feel of built-in applications and add or replace the languages available in the applications. As described in Application options, the underlying mechanism is static hosted web application.
In this tutorial we are publishing 2 web applications:
public-options
, where the JSON file containing the configuration will be storedui-assets
, where any required assets will be hosted: images, favicon and translation files
For deploying we use the nodejs @c8y/cli
that can be installed with the command:
npm install -g @c8y/cli
Downloading or cloning the initial repository
For your convenience you can download or clone the repository available at https://github.com/Cumulocity/ui-customization, in which you can find an example for branding and for adding a new language.
git clone https://github.com/Cumulocity/ui-customization
Inside this folder you can find two other folders:
public-options
ui-assets
Branding options
Edit the file public-options/options.json and change the sub-properties of brandingCssVars
. These properties will be converted into CSS custom properties at runtime.
Note that the properties brand-logo-img
and navigator-platform-logo
are both URLs. Therefore the corresponding files must be placed inside the folder ui-assets.
To change the favicon, edit the property faviconUrl
and/or add the corresponding file inside the ui-assets folder.
To change the browser window title, change the property globalTitle
.
If these configurations are not enough you can still add a list of URLs to the property extraCssUrls
and load extra CSS at runtime:
{
"extraCssUrls": [
"/apps/ui-assets/extra.css"
]
}
Languages
The platform UI strings used for internationalization are stored in gettext. If you want to add a new language to the platform you need a software to edit these files, for example Poedit.
Each translated catalog is loaded at runtime in a JSON format. To convert .po (gettext) files into .json files we rely on @c8y/cli
installed during the first step.
How to add your own translations
- Download the string catalog from @c8y/ngx-components@1004.0.6/locales/locales.pot (the version, 1004.0.6, can be changed to whatever version running on your instance).
- Load the file in your preferred .pot file editor and translate each string to the appropriate language and save that file. Repeat the process for as many languages as you like. The outcome of this step will be a .po catalog file for each language. Make sure to store these files in a safe place, as they will be useful when updating the strings in subsequent versions.
- Transform the newly created .po file into a .json file using the
c8ycli
:
c8ycli locale-compile path/to/language.po
- Copy the generated .json file into the ui-assets folder.
- Update the languages fragment in public-options/options.json.
languages?: {
[langCode: string]: {
name: string;
nativeName: string;
url: string;
}
}
In the example provided in the repository to be downloaded you can find an example of a Russian translation which looks like this:
"languages": {
"ru": {
"name": "Russian",
"nativeName": "русский язык",
"url": "/apps/public/ui-assets/ru.json"
}
}
Deploying
Inside the folder that contains both public-options
and ui-assets
run the command:
c8ycli deploy ui-options ui-assets
Fill in your tenant/instance information and the applications will be deployed and will be visible to that specific tenant and its subtenants.
Info: For performance reasons the options are cached. Therefore the application must be refreshed twice to make the changes visible.