Merge pull request #1024 from sinthuja/application-mgt

Application mgt
merge-requests/7/head
sinthuja 7 years ago committed by GitHub
commit 66f8472da7

@ -20,16 +20,16 @@ package org.wso2.carbon.device.application.mgt.auth.handler.util;
//TODO: Remove hardcoded localhost and ports //TODO: Remove hardcoded localhost and ports
public class Constants { public class Constants {
public static String SCOPES = "perm:application:get perm:application:create perm:application:update " + public static final String SCOPES = "perm:application:get perm:application:create perm:application:update " +
"perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " + "perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " +
"perm:roles:view perm:devices:view perm:platform:get"; "perm:roles:view perm:devices:view perm:platform:get";
public static String[] TAGS = {"device_management"}; public static final String[] TAGS = {"device_management"};
public static String USER_NAME = "userName"; public static final String USER_NAME = "userName";
public static String APPLICATION_NAME = "applicationmgt_publisher"; public static final String APPLICATION_NAME = "applicationmgt_publisher";
public static String TOKEN_ENDPOINT = "https://localhost:8243"; public static final String TOKEN_ENDPOINT = "https://localhost:8243";
public static String PASSWORD_GRANT_TYPE = "password"; public static final String PASSWORD_GRANT_TYPE = "password";
public static String REFRESH_GRANT_TYPE = "refresh_token"; public static final String REFRESH_GRANT_TYPE = "refresh_token";
public static String API_APPLICATION_ENDPOINT = "https://localhost:9443/api-application-registration/"; public static final String API_APPLICATION_ENDPOINT = "https://localhost:9443/api-application-registration/";
public static String APPLICATION_INFO = "application_info"; public static final String APPLICATION_INFO = "application_info";
} }

@ -2,5 +2,10 @@
"theme": { "theme": {
"type": "default", "type": "default",
"value": "lightBaseTheme" "value": "lightBaseTheme"
},
"config": {
"hostname": "localhost",
"httpsPort": "9443",
"apiPort": "8243"
} }
} }

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import axios from 'axios';
import Constants from './constants';
class Configuration {
constructor() {
this.serverConfig = {};
this.hostConstants = {
baseURL: window.location.origin,
appContext: window.location.pathname.split("/")[1]
};
}
loadConfiguration(callback) {
let thisObject = this;
axios.get(thisObject.hostConstants.baseURL + '/' + thisObject.hostConstants.appContext + "/config.json").
then(function (response) {
console.log('server config was read successfully! ');
thisObject.serverConfig = response.data.config;
Constants.load();
callback();
}).catch(function (error) {
console.log('unable to load the config file!' + error);
});
}
}
export default (new Configuration());

@ -15,41 +15,47 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import Configuration from './configuration';
'use strict'; 'use strict';
//TODO: Replace the server address with response from auth endpoint and remove hardcoded ids etc. class Constants {
export default class Constants {
constructor() {
static scopes = 'perm:application:get perm:application:create perm:application:update perm:application-mgt:login' + this.scopes = 'perm:application:get perm:application:create perm:application:update perm:application-mgt:login' +
' perm:application:delete perm:platform:add perm:platform:remove perm:roles:view perm:devices:view'; ' perm:application:delete perm:platform:add perm:platform:remove perm:roles:view perm:devices:view';
this.appManagerEndpoints = {};
static appManagerEndpoints = { this.platformManagerEndpoints = {};
GET_ALL_APPS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/', this.userConstants = {};
CREATE_APP: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/', this.defaultLocale = "en";
UPLOAD_IMAGE_ARTIFACTS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
GET_IMAGE_ARTIFACTS: "https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/image-artifacts/"
};
static platformManagerEndpoints = {
CREATE_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0',
GET_ENABLED_PLATFORMS: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0?status=ENABLED',
GET_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0/'
};
static userConstants = {
LOGIN_URL:"https://localhost:9443/auth/application-mgt/v1.0/auth/login",
LOGOUT_URL: "https://localhost:9443/auth/application-mgt/v1.0/auth/logout",
REFRESH_TOKEN_URL: "",
WSO2_USER: 'wso2_user',
PARTIAL_TOKEN: 'WSO2_IOT_TOKEN'
};
static hostConstants = {
baseURL : window.location.origin,
appContext : window.location.pathname.split("/")[1]
} }
static defaultLocale = "en"; load() {
let apiBaseUrl = 'https://' + Configuration.serverConfig.hostname + ':' + Configuration.serverConfig.apiPort;
let httpBaseUrl = 'https://' + Configuration.serverConfig.hostname + ':' + Configuration.serverConfig.httpsPort;
this.appManagerEndpoints = {
GET_ALL_APPS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/',
CREATE_APP: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/',
UPLOAD_IMAGE_ARTIFACTS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
GET_IMAGE_ARTIFACTS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/image-artifacts/'
};
this.platformManagerEndpoints = {
CREATE_PLATFORM: apiBaseUrl + '/api/application-mgt/v1.0/platforms/1.0.0',
GET_ENABLED_PLATFORMS: apiBaseUrl + '/api/application-mgt/v1.0/platforms/1.0.0?status=ENABLED',
GET_PLATFORM: apiBaseUrl + '/api/application-mgt/v1.0/platforms/1.0.0/'
};
this.userConstants = {
LOGIN_URL: httpBaseUrl + '/auth/application-mgt/v1.0/auth/login',
LOGOUT_URL: httpBaseUrl + '/auth/application-mgt/v1.0/auth/logout',
REFRESH_TOKEN_URL: "",
WSO2_USER: 'wso2_user',
PARTIAL_TOKEN: 'WSO2_IOT_TOKEN'
};
}
} }
export default(new Constants);

@ -57,46 +57,6 @@ class ApplicationListing extends Component {
}; };
} }
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "applicationName",
data_type: "string",
sortable: true,
label: "Application Name",
sort: this.sortData
},
{
data_id: "platform",
data_type: "image_array",
sortable: false,
label: "Platform"
},
{
data_id: "category",
data_type: "string",
sortable: false,
label: "Category"
},
{
data_id: "status",
data_type: "string",
sortable: false,
label: "Status"
},
{
data_id: "edit",
data_type: "button",
sortable: false,
label: ""
}
];
applications = [ applications = [
{ {

@ -24,34 +24,40 @@ import registerServiceWorker from './registerServiceWorker';
import {IntlProvider, addLocaleData, defineMessages} from 'react-intl'; import {IntlProvider, addLocaleData, defineMessages} from 'react-intl';
import Axios from 'axios'; import Axios from 'axios';
import Constants from './common/constants'; import Constants from './common/constants';
import Configuration from './common/configuration';
const possibleLocale = navigator.language.split("-")[0]; function loadPublisher() {
let loadLocaleFile = Axios.create({ const possibleLocale = navigator.language.split("-")[0];
baseURL: Constants.hostConstants.baseURL + "/" + Constants.hostConstants.appContext + "/locales/" let loadLocaleFile = Axios.create({
+ possibleLocale + ".json" baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales/"
}).get(); + possibleLocale + ".json"
}).get();
/** /**
* This is the base js file of the app. All the content will be rendered in the root element. * This is the base js file of the app. All the content will be rendered in the root element.
* */ * */
loadLocaleFile.then(response => { loadLocaleFile.then(response => {
const messages = defineMessages(response.data);
addLocaleData(require('react-intl/locale-data/' + possibleLocale));
ReactDOM.render(<IntlProvider locale={possibleLocale}
messages={messages}><Publisher/></IntlProvider>, document.getElementById('root'));
registerServiceWorker();
}).catch(error => {
addLocaleData(require('react-intl/locale-data/' + Constants.defaultLocale));
let defaultLocale = axios.create({
baseURL: Constants.hostConstants.baseURL + "/" + Constants.hostConstants.appContext + "/locales"
+ Constants.defaultLocale + ".json"
}).get();
defaultLocale.then(response => {
const messages = defineMessages(response.data); const messages = defineMessages(response.data);
addLocaleData(require('react-intl/locale-data/' + possibleLocale));
ReactDOM.render(<IntlProvider locale={possibleLocale} ReactDOM.render(<IntlProvider locale={possibleLocale}
messages={messages}><Publisher/></IntlProvider>, document.getElementById('root')); messages={messages}><Publisher/></IntlProvider>, document.getElementById('root'));
registerServiceWorker(); registerServiceWorker();
}).catch(error => { }).catch(error => {
addLocaleData(require('react-intl/locale-data/en'));
let defaultLocale = axios.create({
baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales"
+ Constants.defaultLocale + ".json"
}).get();
defaultLocale.then(response => {
const messages = defineMessages(response.data);
ReactDOM.render(<IntlProvider locale={possibleLocale}
messages={messages}><Publisher/></IntlProvider>, document.getElementById('root'));
registerServiceWorker();
}).catch(error => {
});
}); });
}); }
Configuration.loadConfiguration(loadPublisher);

@ -1,46 +1,59 @@
{ {
"name": "store", "name": "store",
"version": "0.1.0", "version": "1.0.0",
"private": true, "description": "WSO2 IoT Server App Store",
"main": "App.js",
"repository": {
"type": "git",
"url": "git://github.com/wso2/carbon-devicemgt"
},
"license": "Apache License 2.0",
"dependencies": { "dependencies": {
"axios": "^0.16.2", "axios": "^0.16.2",
"bootstrap": "^4.0.0-alpha.6",
"flux": "^3.1.3", "flux": "^3.1.3",
"history": "^4.6.3", "history": "^4.7.2",
"latest-version": "^3.1.0", "latest-version": "^3.1.0",
"material-ui": "^0.19.0", "material-ui": "^0.19.1",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"qs": "^6.5.0", "qs": "^6.5.0",
"react": "^15.6.1", "react": "^15.6.1",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-transition-group": "^15.6.0",
"react-dom": "^15.6.1", "react-dom": "^15.6.1",
"react-dropzone": "^4.1.0", "react-dropzone": "^4.1.2",
"react-images-uploader": "^1.1.0", "react-images-uploader": "^1.1.0",
"react-material-ui-form-validator": "^0.5.0", "react-material-ui-form-validator": "^0.5.1",
"react-modal": "^2.2.2", "react-modal": "^2.3.2",
"react-router": "^4.1.2", "react-router": "^4.2.0",
"react-router-dom": "^4.1.2", "react-router-dom": "^4.2.2",
"react-scripts": "1.0.10", "react-scripts": "1.0.10",
"react-sliding-pane": "^1.2.3", "react-sliding-pane": "^1.2.3",
"react-tap-event-plugin": "^2.0.1" "react-tap-event-plugin": "^2.0.1",
"reactstrap": "^4.8.0"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.24.1", "babel-core": "^6.26.0",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babel-register": "^6.24.1", "babel-register": "^6.26.0",
"chai": "^4.0.2", "chai": "^4.1.2",
"css-loader": "^0.28.7", "css-loader": "^0.28.7",
"less": "^2.7.2", "less": "^2.7.2",
"less-loader": "^4.0.4", "less-loader": "^4.0.4",
"mocha": "^3.4.1", "mocha": "^3.4.1",
"mock-local-storage": "^1.0.2", "mock-local-storage": "^1.0.2",
"node-sass": "^4.5.3",
"react-intl": "^2.4.0",
"sass-loader": "^6.0.6",
"style-loader": "^0.18.1", "style-loader": "^0.18.1",
"webpack": "^2.7.0" "webpack": "^2.7.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build --history-api-fallback",
"test": "react-scripts test --env=jsdom", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"build_prod": "NODE_ENV=production webpack -p --progress --colors --config webpack.config.js", "build_prod": "NODE_ENV=production webpack -p --progress --colors --config webpack.config.js",

@ -0,0 +1,11 @@
{
"theme": {
"type": "default",
"value": "lightBaseTheme"
},
"config": {
"hostname": "localhost",
"httpsPort": "9443",
"apiPort": "8243"
}
}

@ -1,15 +1,34 @@
<!doctype html> <!doctype html>
<!--
~ Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000"> <meta name="theme-color" content="#000000">
<link rel="stylesheet" type="text/css" href="/store/css/font-wso2.css">
<link rel="stylesheet" type="text/css" href="/store/themes/default/default-theme.css">
<!-- <!--
manifest.json provides metadata used when your web app is added to the manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="manifest" href="/store/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <link rel="shortcut icon" href="/store/images/favicon.png">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
@ -19,13 +38,14 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>WSO2 IoT App Store</title>
</head> </head>
<body> <body>
<noscript> <noscript>
You need to enable JavaScript to run this app. You need to enable JavaScript to run this app.
</noscript> </noscript>
<div id="root"></div> <div id="root"></div>
<script src='/store/dist/index.js'></script>
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.

@ -0,0 +1,41 @@
{
"Title" : "Title",
"Description" : "Description",
"Category" : "Category",
"Visibility" : "Visibility",
"Devices" : "Devices",
"Roles" : "Roles",
"Groups" : "Groups",
"Tags" : "Tags",
"Platform" : "Platform",
"Platforms" : "Platfomrs",
"No.Platform" : "No Platforms",
"Screenshots" : "Screenshots",
"Icon" : "Icon",
"Banner" : "Banner",
"Create.Application" : "Create Application",
"Back" : "Back",
"Cancel" : "Cancel",
"Finish" : "Finish",
"Continue" : "Continue",
"Application.Name" : "Application Name",
"General" : "General",
"App.Releases" : "Application Releases",
"Package.Manager" : "Package Manager",
"Save" : "Save",
"Create.Release" : "Create Release",
"Release" : "Release",
"New.Release.For" : "New Release for",
"Upload.Package.File" : "Upload Package File",
"Upload" : "Upload",
"Select.from.package.library" : "Select from package library",
"Release.Name" : "Release Name",
"Release.Notes" : "Release Notes",
"Send.for.Review" : "Send for Review",
"Production.Releases" : "Production Releases",
"Beta.Releases" : "Beta Releases",
"Alpha.Releases" : "Alpha Releases",
"Version" : "Version",
"Status" : "Status",
"App.Publisher" : "Application Publisher"
}

@ -1,10 +1,10 @@
{ {
"short_name": "React App", "short_name": "App Store",
"name": "Create React App Sample", "name": "WSO2 IoT App Store",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "images/favicon.png",
"sizes": "192x192", "sizes": "16x16",
"type": "image/png" "type": "image/png"
} }
], ],

@ -0,0 +1,66 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.image-container {
position: relative;
width: 100%;
height: 100%;
margin: 5px;
}
.image {
opacity: 1;
display: block;
max-width: 100%;
max-height: 100%;
height: auto;
transition: .5s ease;
backface-visibility: hidden;
width: 100%;
}
.btn-content {
transition: .5s ease;
opacity: 0;
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(243, 243, 243, 0.43);
border-radius: 50%;
border: solid 1px #ffffff;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%)
}
.image-container:hover .image {
opacity: 0.3;
}
.image-container:hover .btn-content {
opacity: 1;
}
.close-btn {
color: #000000;
font-size: 16px;
padding: 20px 30px;
}
.close-btn:hover {
cursor: pointer;
}

@ -0,0 +1,48 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.chip {
display: inline-block;
padding: 0 25px;
height: 50px;
font-size: 16px;
line-height: 50px;
border-radius: 25px;
background-color: #f1f1f1;
}
.chip img {
float: left;
margin: 0 10px 0 -25px;
height: 50px;
width: 50px;
border-radius: 50%;
}
.close-btn {
padding-left: 10px;
color: #888;
font-weight: bold;
float: right;
font-size: 20px;
cursor: pointer;
}
.close-btn:hover {
color: #000;
}

@ -0,0 +1,483 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* Body Styling */
body {
width: 100%;
font-family: Roboto sans-serif;
}
/* Login page styles*/
#userName {
border-radius: 0;
}
#password {
border-radius: 0;
}
#login-btn {
border-radius: 0;
background-color: navy;
color: white;
cursor: pointer;
}
#login-container {
width: 50%;
margin: 0 auto
}
#login-card {
border-radius: 0;
background-color: #BaBaBa;
}
/* Base layout container */
#container {
background-color: #ffffff;
padding: 0;
}
/* Base layout header content*/
#header-content {
height: 125px;
width: 100%;
margin: 0 10px 0 0;
background-color: #3b33bd;
position: fixed; /* Set the navbar to fixed position */
top: 0; /* Position the navbar at the top of the page */
z-index: 2;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
/* Contains the header styles.*/
#header {
margin: 16px 16px 20px 16px;
height: 100%;
position: relative;
}
#header-text {
color: #ffffff;
font-size: 25px;
top: 10px;
margin-left: 10px;
}
/* The buttons in the header (User and Notification)*/
#header-btn-container {
float: right;
}
.btn-header {
margin-top: 15px;
margin-right: 20px;
color: white;
}
/* Search box styles */
.search-icon {
position: absolute;
top: 5px;
left: 5px;
}
#search-box {
display: flex;
color: #a8a8a8;
position: relative;
float: right;
top: 75px;
left: 80px;
margin-right: 16px;
}
#search {
position: relative;
color: white;
background-color: transparent;
left: 15px;
top: 0px;
height: 25px;
outline: none;
border: none;
border-radius: 0%;
}
/* Application Add button */
#add-btn-container {
position: absolute;
left: 12%;
top: 100px;
}
/* Holds the app publisher pages. */
#application-content {
height: auto;
width: 80%;
margin: 150px auto;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 10px 10px 10px 10px;
}
.stepper-header {
width: 100%;
border-bottom: solid 1px cornflowerblue;
padding-bottom: 5px;
margin-bottom: 10px;
}
.platform-link-placeholder {
color: #888888;
float: right;
margin-right: 20px;
padding-bottom: 10px;
}
#application-list {
margin-top: 20px;
transition: margin-right .5s;
}
#app-image-screenshot {
width: 300px;
height: 300px;
}
#app-image-icon {
width: 300px;
height: 300px;
}
#app-image-banner {
width: 400px;
height: 300px;
}
.application-create-banner-dropzone {
width: 300px;
height: 150px;
border-radius: 5%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-banner-dropzone i {
position: absolute;
top: 65px;
left: 145px;
}
.application-create-screenshot-dropzone {
width: 150px;
height: 150px;
margin: 0 5px 0 5px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-screenshot-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
.application-create-icon-dropzone {
width: 150px;
height: 150px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-icon-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
#screenshot-container {
display: flex;
overflow-x: auto;
height: 200px;
}
#app-icon-container {
height: 300px;
overflow-x: auto;
}
#modal-body-content {
max-height: 700px;
overflow-y: auto;
}
#img-btn-screenshot {
margin: 0 5px 0 5px;
}
#app-create-modal {
max-width: 700px;
overflow-x: auto;
}
#store {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#version {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#app-release-switch-content {
display: flex;
}
#app-release-switch-label {
position: absolute;
float: left;
}
#app-release-switch-switch {
position: absolute;
right: 10px;
}
.image-sub-title {
font-style: italic;
font-size: 12px;
color: #818181;
}
/* Application View */
#application-view-content {
width: 100%;
}
#application-view-row {
margin: 10px 10px 20px 20px;
}
#app-icon {
height: 100px;
width: 100px;
border: solid 1px black;
border-radius: 50%;
}
.app-updated-date {
color: #888888;
font-style: italic;
}
.app-install-count {
font-style: italic;
}
.app-details-tbl {
outline: none;
border-color: #2196F3;
}
.app-details-tbl tr {
margin: 20px 0 0 0;
}
.app-details-tbl td {
margin-left: 10px;
max-width: 400px;
}
/* Application Edit Base Layout */
#application-edit-header {
height: 50px;
width: 100%;
margin: 0;
font-size: 20px;
border-bottom: solid 1px #d8d8d8;
}
.application-header-text {
margin: 10px 0px 0px 10px;
}
#save-btn-content {
float: right;
}
#app-save-btn {
border-radius: 0%;
}
.save-btn {
margin: 5px 5px 5px 0px;
height: 70%;
width: 50%;
float: right;
}
.save-btn:hover {
cursor: pointer;
}
/*Tab styling*/
div.tab {
float: left;
border-right: 1px solid #d8d8d8;
height: 100%;
}
/* Style the tab buttons */
div.tab button {
display: block;
background-color: inherit;
color: black;
padding: 15px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
}
/* Change background color of buttons on hover */
div.tab button:hover {
background-color: #ddd6d7;
cursor: pointer;
}
/* Create an active/current "tab button" class */
div.tab button.active {
background-color: #1b3bcc;
color: white;
}
#application-edit-main-container {
display: flex;
}
#application-edit-outer-content {
height: auto;
width: 100%;
}
#application-edit-content {
margin: 5px 10px 5px 10px;
width: 90%;
}
#app-edit-content {
height: 100%;
position: relative;
}
.back-to-app {
position: absolute;
height: 40px;
width: 40px;
border-radius: 50%;
}
.back-to-app i {
padding: 10px 10px 10px 10px;
}
.back-to-app:hover {
cursor: pointer;
background-color: #dedede;
transition: .5s;
}
/* Create Release and Release management */
.release-header {
margin-top: 20px;
margin-bottom: 20px;
}
.release-create {
height: 150px;
margin-bottom: 20px;
}
.release-detail-content {
width: 100%;
margin-top: 20%;
height: 300px;
}
.form-btn {
float: right;
margin-bottom: 10px;
}
.release-content {
height: 180px;
width: 95%;
border: dashed 1px #626262;
border-radius: 2%;
position: relative;
background-color: #e8e8e8;
}
.release-content:after {
content: "";
letter-spacing: 4px;
}
.release {
margin: 30px 10px 20px 30px;
}
.no-release-content {
position: absolute;
margin-top: 10px;
left: 40%;
}
.button-add:hover {
cursor: pointer;
}
.release-inner {
margin-top: 5%;
}
/* Application Edit General Info */
.app-edit-general-info {
margin-top: 20px;
max-width: 100%;
}
.save-info {
float: right;
margin-bottom: 10px;
}

@ -0,0 +1,73 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.app-view-drawer {
height: 100%; /* 100% Full-height */
width: 0; /* 0 width - change this with JavaScript */
position: fixed; /* Stay in place */
z-index: 1; /* Stay on top */
top: 8%;
right: 0%;
background-color: #ffffff;
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 60px; /* Place content 60px from the top */
transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.app-view-drawer a {
padding: 8px 8px 8px 32px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s
}
/* Position and style the close button (top right corner) */
.app-view-drawer .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
.drawer-close-btn {
height: 40px;
width: 30px;
}
.drawer-close-btn:hover {
cursor: pointer;
color: #818181;
}
/* Style page content - use this if you want to push the page content to the right when you open the side navigation */
#main {
transition: margin-left .5s;
padding: 20px;
}
/* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
@media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}

@ -0,0 +1,50 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.btn-circle {
color: white;
position: relative;
background-color: #e65100;
border-radius: 50%;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.btn-circle i {
position: absolute;
margin-top: 37%;
margin-left: 37%;
}
.small {
height: 50px;
width: 50px;
}
.medium {
height: 100px;
width: 100px;
}
.btn-circle:hover {
cursor: pointer;
background-color: rgb(255, 93, 2);
}
.primary {
background-color: blue;
}

@ -0,0 +1,18 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.notification-app-icon {
border-radius: 50%;
border: solid 1px black;
}
.small {
height: 50px;
width: 50px;
}
.medium {
height: 100px;
width: 100px;
}
#notification-view-content {
width: 50%;
border: solid 1px black;
margin: 0 auto;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#notification-content {
margin: 20px 10px 20px 10px;
}
#app-reject-msg {
width: 100%;
height: 30px;
background-color: #888888;
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 24px;
}
/* Hide default HTML checkbox */
.switch input {display:none;}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(16px);
-ms-transform: translateX(16px);
transform: translateX(16px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

@ -17,26 +17,18 @@
*/ */
import React, {Component} from 'react'; import React, {Component} from 'react';
import AuthHandler from './api/authHandler';
import createHistory from 'history/createBrowserHistory'; import createHistory from 'history/createBrowserHistory';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom' import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; import {
import getMuiTheme from 'material-ui/styles/getMuiTheme'; ApplicationListing,
import Login from './components/Login'; BaseLayout,
import BaseLayout from './components/BaseLayout'; Login,
import NotFound from './components/NotFound'; NotFound
} from './components';
const history = createHistory({basename: '/store'});
/** const history = createHistory({basename: '/store'});
* User can define the themes in the config.json. The themes will be loaded based on the user preference.
*/
const theme = require("./config.json").theme;
//
let muiTheme = null;
if (theme.current === "default") {
let defaultTheme = require("material-ui/styles/baseThemes/" + theme.default);
muiTheme = getMuiTheme(defaultTheme.default);
}
/** /**
* This component defines the layout and the routes for the app. * This component defines the layout and the routes for the app.
@ -46,68 +38,86 @@ if (theme.current === "default") {
* The Router and Route components. * The Router and Route components.
* The Router and Route is used for navigation. * The Router and Route is used for navigation.
* We specify the component which needs to be rendered for an URL. * We specify the component which needs to be rendered for an URL.
* Ex: When navigate to publisher/overview, the overview component will be rendered inside the main layout. * Ex: When navigate to store/overview, the overview component will be rendered inside the main layout.
* *
* HashRouter is used because the other router types need the server to serve those urls. In hashRouter, server does * HashRouter is used because the other router types need the server to serve those urls. In hashRouter, server does
* not want to serve the URL. * not want to serve the URL.
* */ * */
class Base extends Component { class Base extends Component {
constructor() {
super();
this.state = {
user: null
}
}
componentWillMount() {
let user = AuthHandler.getUser();
if (user) {
if (!AuthHandler.isTokenExpired()) {
this.setState({user: user});
} else {
this.setState({user: null});
}
}
}
render() { render() {
return ( if (this.state.user !== null) {
<div className="container"> return (
<BaseLayout state={this.props.state} updateState={this.props.updateState}> <div>
<Switch> <BaseLayout user={this.state.user}>
<Route component={NotFound}/> <Switch>
</Switch> <Redirect exact path={"/"} to={"/assets/apps"}/>
</BaseLayout> <Route exact path={"/assets/apps"} component={ApplicationListing}/>
</div> <Route exact path={"/assets/reviews"}/>
) <Route exact path={"/assets/reviews/:review"}/>
<Route component={NotFound}/>
</Switch>
</BaseLayout>
</div>
);
} else {
return (
<div>
<BaseLayout>
<Switch>
<Redirect exact path={"/"} to={"/assets/apps"}/>
<Route exact path={"/assets/apps"} component={ApplicationListing}/>
<Route component={NotFound}/>
</Switch>
</BaseLayout>
</div>
);
}
} }
} }
Base.propTypes = {
updateState: React.PropTypes.func.isRequired
};
/** /**
* This component is referred by the index.js to initiate the application. * This component is referred by the index.js to initiate the application.
* TODO: Currently the URL shows like https://localhost:9443/publisher/#/publisher/assets/apps/create. this needs to * TODO: Currently the URL shows like https://localhost:9443/store/#/store/assets/apps/create. this needs to
* be fixed as https://localhost:9443/publisher/#/assets/apps/create * be fixed as https://localhost:9443/store/#/assets/apps/create
* *
* */ * */
class Store extends Component { class Store extends Component {
constructor() { constructor() {
super(); super();
if (!this.state) {
this.state = {};
this.state.store = {};
}
this.updateState = this.updateState.bind(this);
} }
render() { render() {
return ( return (
<div className="App"> <div className="App">
<MuiThemeProvider muiTheme={muiTheme}> <Router basename="store" history={history}>
<Router basename="store" history={history}> <Switch>
<Switch> <Route path="/login" component={Login}/>
<Route path="/login" <Route path="/logout" component={Login}/>
render={routeProps => <Login {...routeProps} updateState={this.updateState} state={this.state}/>}/> <Route component={Base}/>
<Route path="/logout" </Switch>
render={routeProps => <Base {...routeProps} updateState={this.updateState} state={this.state}/>}/> </Router>
<Route
render={routeProps => <Base {...routeProps} updateState={this.updateState} state={this.state}/>}/>
</Switch>
</Router>
</MuiThemeProvider>
</div> </div>
); );
} }
updateState(data) {
this.setState(data);
}
} }
export default Store; export default Store;

@ -0,0 +1,26 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});

@ -0,0 +1,138 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import AuthHandler from './authHandler';
import Constants from '../common/constants';
import Helper from './helpers/appMgtApiHelpers';
/**
* Api definitions related to application management.
* TODO: Work to be done on Application release.
* */
export default class ApplicationMgtApi {
/**
* Api for create an application.
* @param: applicationData: The application data object. This contains an object array of each step data from
* application creation wizard.
*
* From applicationData, the proper application object will be created and send it to the api.
* */
static createApplication(applicationData) {
let {application, images} = Helper.buildApplication(applicationData);
const headers = AuthHandler.createAuthenticationHeaders("application/json");
console.log(application);
console.log(images);
Axios.post(Constants.appManagerEndpoints.CREATE_APP, application, {headers: headers});
}
/**
* Upload the image artifacts (banner, icon, screenshots) related to the application.
* @param appId: The application uuid of the application which the images should be uploaded to.
* @param images: The images object. This contains icon, banner and screenshots.
* */
static uploadImageArtifacts(appId, images) {
let formData = new FormData();
formData.append('icon', images.icon);
formData.append('banner', images.banner);
formData.append('screenshot', images.screenshots);
console.log("Image", formData);
const headers = AuthHandler.createAuthenticationHeaders("multipart/form-data");
return Axios.post(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers});
}
/**
* Method to handle application release process.
* */
static releaseApplication(appId) {
}
/**
* Promote the current life cycle state of the application.
* @param appId: The uuid of the application which the state should be updated.
* @param nextState: The next lifecycle state that the application can be updated to.
*
* URL Pattern : /application/1.0/
* */
static updateLifeCycleState(appId, nextState) {
}
/**
* Get the next possible state, which the application can be promoted to.
* @param appId: The application uuid.
*/
static getNextLifeCycleState(appId) {
}
/**
* Edit created application.
* @param applicationData: The modified application data.
* */
static editApplication(applicationData) {
let app = Helper.buildApplication(applicationData).application;
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.put(Constants.appManagerEndpoints.CREATE_APP, app, {headers: headers});
}
static getApplicationArtifacts(appId, artifactName) {
const headers = AuthHandler.createAuthenticationHeaders("image/png");
return Axios.get(Constants.appManagerEndpoints.GET_IMAGE_ARTIFACTS + appId + "?name=" + artifactName,
{headers: headers});
}
static editApplicationArtifacts(appId, images) {
let formData = new FormData();
formData.append('icon', images.icon);
formData.append('banner', images.banner);
formData.append('screenshot', images.screenshots);
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.put(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers});
}
/**
* Get all the created applications for the user.
* @return Object: The response object from the axios post.
* */
static getApplications() {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS, {headers: headers});
}
/**
* Get specific application.
* @param appId: The application Id.
* */
static getApplication(appId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers});
}
/**
* Delete specified application.
* @param appId: The id of the application which is to be deleted.
* */
static deleteApplication(appId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.delete(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers});
}
}

@ -0,0 +1,155 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import User from './data/user';
import Utils from './data/utils';
import Constants from "../common/constants";
/**
* Handles all tasks related to Authentication and Authorization.
* Generate access tokens, verify the user has necessary permissions etc.
* */
class AuthHandler {
/**
* Sends a request to the auth handler endpoint (auth/application-mgt/v1.0/auth/login) and generate token pair.
* @param userName: The user name of the user.
* @param password: The user password.
* @return Object: The response object from the axios post.
* */
static login(userName, password) {
const headers = {"Content-type": "application/json"};
let login_promise =
Axios.post(Constants.userConstants.LOGIN_URL+"?userName=" + userName+ "&password=" + password,
null, {headers: headers});
login_promise.then(response => {
console.log(response);
const userName = response.data.userName;
const validityPeriod = response.data.expires_in; // In seconds
const WSO2_IOT_TOKEN = response.data.access_token;
const refreshToken = response.data.refresh_token;
const clientId = response.data.application_info[0].consumerKey;
const clientSecret = response.data.application_info[0].consumerSecret;
const user = new User(userName, clientId, clientSecret, validityPeriod);
console.log(user);
user.setAuthToken(WSO2_IOT_TOKEN, validityPeriod);
let expiresIn = Date.now() + (validityPeriod * 1000);
localStorage.setItem("expiresIn", expiresIn);
AuthHandler.setUser(user);
}
);
return login_promise;
};
static loginAsAnonymous(){
}
/**
* Persists the user object in browser's local storage.
* @param user: The user object.
* */
static setUser(user) {
if (!user instanceof User) {
throw "Invalid user object";
}
user.created = Date.now();
localStorage.setItem(Constants.userConstants.WSO2_USER, JSON.stringify(user.toJson()));
/* TODO: IMHO it's better to get this key (`wso2_user`) from configs */
}
static unauthorizedErrorHandler(error_response) {
if (error_response.status !== 401) { /* Skip unrelated response code to handle in unauthorizedErrorHandler*/
throw error_response;
/* re throwing the error since we don't handle it here and propagate to downstream error handlers in catch chain*/
}
let message = "The session has expired" + ".<br/> You will be redirect to the login page ...";
if (true) {
alert(message);
} else {
throw error_response;
}
}
/**
* Get the logged in user.
* @return User: The logged in user object.
* */
static getUser() {
const userData = localStorage.getItem(Constants.userConstants.WSO2_USER);
const partialToken = Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
if (!(userData && partialToken)) {
return null;
}
return User.fromJson(JSON.parse(userData));
}
isLoggedIn() {
}
static logout() {
const user = AuthHandler.getUser();
const clientId = user.getClientId();
const clientSecret = user.getClientSecret();
const token = user.getAuthToken();
const headers = {"Content-type": "application/json"};
let login_promise = Axios.post(Constants.userConstants.LOGOUT_URL+"?token=" + token + "&clientId=" + clientId
+ "&clientSecret=" + clientSecret,
null, {headers: headers});
login_promise.then(
(response) => {
Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN);
localStorage.removeItem(Constants.userConstants.WSO2_USER);
window.location = "/";
}
).catch(
(err) => {
AuthHandler.unauthorizedErrorHandler(err);
}
)
}
/**
* Checks whether the access token is expired.
* @return boolean: True if expired. False otherwise.
* */
static isTokenExpired() {
const expiresIn = localStorage.getItem("expiresIn");
return (expiresIn < Date.now());
}
static createAuthenticationHeaders(contentType) {
if (AuthHandler.getUser().getAuthToken()) {
return {
"Authorization": "Bearer " + AuthHandler.getUser().getAuthToken(),
"Content-Type": contentType,
};
}
return "User not found";
};
}
export default AuthHandler;

@ -0,0 +1,118 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
"use strict";
import Utils from './utils'
import Constants from '../../common/constants';
/**
* Represent an user logged in to the application, There will be allays one user per session and
* this user details will be persist in browser localstorage.
*/
export default class User {
constructor(name, clientId, clientSecret, validityPeriod) {
if (User._instance) {
return User._instance;
}
this._userName = name;
this._clientId = clientId;
this._clientSecret = clientSecret;
this._expires = validityPeriod;
User._instance = this;
}
/**
* OAuth scopes which are available for use by this user
* @returns {Array} : An array of scopes
*/
get scopes() {
return this._scopes;
}
/**
* Set OAuth scopes available to be used by this user
* @param {Array} newScopes : An array of scopes
*/
set scopes(newScopes) {
Object.assign(this.scopes, newScopes);
}
/**
* Get the JS accessible access token fragment from cookie storage.
* @returns {String|null}
*/
getAuthToken() {
return Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
}
getClientId() {
return this._clientId;
}
getClientSecret() {
return this._clientSecret;
}
/**
* Store the JavaScript accessible access token segment in cookie storage
* @param {String} newToken : Part of the access token which needs when accessing REST API
* @param {Number} validityPeriod : Validity period of the cookie in seconds
*/
setAuthToken(newToken, validityPeriod) {
Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN);
Utils.setCookie(Constants.userConstants.PARTIAL_TOKEN, newToken, validityPeriod);
}
/**
* Get the user name of logged in user.
* @return String: User name
* */
getUserName() {
return this._userName;
}
/**
* Provide user data in JSON structure.
* @returns {JSON} : JSON representation of the user object
*/
toJson() {
return {
name: this._userName,
clientId: this._clientId,
clientSecret: this._clientSecret,
expires: this._expires
};
}
/**
* User utility method to create an user from JSON object.
* @param {JSON} userJson : Need to provide user information in JSON structure to create an user object
* @returns {User} : An instance of User(this) class.
*/
static fromJson(userJson) {
const _user = new User(userJson.name);
_user._clientId = userJson.clientId;
_user._clientSecret = userJson.clientSecret;
_user._expires = userJson.expires;
console.log(_user);
return _user;
}
}
User._instance = null; // A private class variable to preserve the single instance of a swaggerClient

@ -0,0 +1,92 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Utility class for Publisher application
*/
class StoreUtils {
/**
* TODO: Remove this method one the initial phase is done, This is used to continue the API class until the login page is create
* @returns {promise}
*/
// static autoLogin() {
// let auth = new AuthManager();
// return auth.authenticateUser('admin', 'admin');
// }
/**
* Get JavaScript accessible cookies saved in browser, by giving the cooke name.
* @param {String} name : Name of the cookie which need to be retrived
* @returns {String|null} : If found a cookie with given name , return its value,Else null value is returned
*/
static getCookie(name) {
let pairs = document.cookie.split(";");
let cookie = null;
for (let pair of pairs) {
pair = pair.split("=");
let cookie_name = pair[0].trim();
let value = encodeURIComponent(pair[1]);
if (cookie_name === name) {
cookie = value;
break;
}
}
return cookie;
}
/**
* Delete a browser cookie given its name
* @param {String} name : Name of the cookie which need to be deleted
*/
static delete_cookie(name) {
document.cookie = name + '=; Path=' + "/" + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
/**
* Set a cookie with given name and value assigned to it. Cookies can be only set to the same origin,
* which the script is running
* @param {String} name : Name of the cookie which need to be set
* @param {String} value : Value of the cookie, expect it to be URLEncoded
* @param {number} validityPeriod : (Optional) Validity period of the cookie in seconds
* @param {String} path : Path which needs to set the given cookie
* @param {boolean} secured : secured parameter is set
*/
static setCookie(name, value, validityPeriod, path = "/", secured = true) {
let expires = "";
const securedDirective = secured ? "; Secure" : "";
if (validityPeriod) {
const date = new Date();
date.setTime(date.getTime() + validityPeriod * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + value + expires + "; path=" + path + securedDirective + validityPeriod
}
/**
* Given an object returns whether the object is empty or not
* @param {Object} object : Any JSON object
* @returns {boolean}
*/
static isEmptyObject(object) {
return Object.keys(object).length === 0 && object.constructor === Object
}
}
export default StoreUtils;

@ -0,0 +1,64 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
/**
* Helper methods for app store.
* */
export default class Helper {
/**
* Generate application object from form data passed.
* @param appData: Application data from the application creation form.
* @return {Object, Object}: The application object and the set of images related to the application.
* */
static buildApplication(appData) {
let application = {};
let images = {};
for (let step in appData) {
let tmpData = appData[step].data.step;
for (let prop in tmpData) {
if (prop === 'banner' || prop === 'screenshots' || prop === 'icon') {
images[prop] = tmpData[prop];
} else if(prop === 'tags') {
application[prop] = Helper.stringifyTags(tmpData[prop]);
} else {
application[prop] = tmpData[prop];
}
}
}
return {application, images};
}
/**
* Creates a String array from tags array.
* */
static stringifyTags(tags) {
let tmpTags = [];
for (let tag in tags) {
console.log(tag);
tmpTags.push(tags[tag].value);
}
return tmpTags;
}
}

@ -0,0 +1,68 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import AuthHandler from './authHandler';
import Constants from '../common/constants';
/**
* Api definitions for Platform management.
* */
export default class PlatformMgtApi{
/**
* Create a new Platform
* @param platformData: The platform data object.
* */
static createPlatform(platformData) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
Axios.post(Constants.platformManagerEndpoints.CREATE_PLATFORM, platformData, {headers: headers}).then(
function (response) {
console.log(response);
}
).catch(function (err) {
console.log(err);
});
}
/**
* Get available platforms
* */
static getPlatforms() {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.platformManagerEndpoints.GET_ENABLED_PLATFORMS, {headers: headers});
}
/**
* Get the user specified platform
* @param platformId: The identifier of the platform
* */
static getPlatform(platformId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers});
}
/**
* Delete specified platform
* @param platformId: The id of the platform which is to be deleted.
* */
static deletePlatform(platformId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.delete(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers});
}
}

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import axios from 'axios';
import Constants from './constants';
class Configuration {
constructor() {
this.serverConfig = {};
this.hostConstants = {
baseURL: window.location.origin,
appContext: window.location.pathname.split("/")[1]
};
}
loadConfiguration(callback) {
let thisObject = this;
axios.get(thisObject.hostConstants.baseURL + '/' + thisObject.hostConstants.appContext + "/config.json").
then(function (response) {
console.log('successfully loaded the configuration!');
thisObject.serverConfig = response.data.config;
Constants.load();
callback();
}).catch(function (error) {
console.log('unable to load the config file!' + error);
});
}
}
export default (new Configuration());

@ -0,0 +1,63 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Configuration from './configuration';
'use strict';
//TODO: Replace the server address with response from auth endpoint and remove hardcoded ids etc.
class Constants {
constructor() {
this.scopes = 'perm:application:get perm:application:create perm:application:update perm:application-mgt:login' +
' perm:application:delete perm:platform:add perm:platform:remove perm:roles:view perm:devices:view';
this.appManagerEndpoints = {};
this.platformManagerEndpoints = {};
this.userConstants = {};
this.defaultLocale = "en";
}
load() {
let apiBaseUrl = 'https://' + Configuration.serverConfig.hostname + ':' + Configuration.serverConfig.apiPort;
let httpBaseUrl = 'https://' + Configuration.serverConfig.hostname + ':' + Configuration.serverConfig.httpsPort;
this.appManagerEndpoints = {
GET_ALL_APPS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/',
CREATE_APP: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/',
UPLOAD_IMAGE_ARTIFACTS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
GET_IMAGE_ARTIFACTS: apiBaseUrl + '/api/application-mgt/v1.0/applications/1.0.0/image-artifacts/'
};
//TODO: remove the platform manager endpoints
this.platformManagerEndpoints = {
CREATE_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0',
GET_ENABLED_PLATFORMS: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0?status=ENABLED',
GET_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0/'
};
this.userConstants = {
LOGIN_URL: httpBaseUrl + '/auth/application-mgt/v1.0/auth/login',
LOGOUT_URL: httpBaseUrl + '/auth/application-mgt/v1.0/auth/logout',
REFRESH_TOKEN_URL: "",
WSO2_USER: 'wso2_user',
PARTIAL_TOKEN: 'WSO2_IOT_TOKEN'
};
}
}
export default(new Constants);

@ -0,0 +1,117 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import AuthHandler from "../../api/authHandler";
import {Col, Container, Input, Row,} from 'reactstrap';
import FloatingButton from "../UIComponents/FloatingButton/FloatingButton";
/**
* Base Layout:
* App bar
* Left Navigation
* Middle content.
* */
class BaseLayout extends Component {
constructor() {
super();
this.state = {
notifications: 0,
user: 'Admin',
openModal: false
};
this.logout = this.logout.bind(this);
this.closeModal = this.closeModal.bind(this);
}
handleApplicationClick() {
this.handleHistory('/assets/apps');
}
handleApplicationCreateClick(event) {
event.preventDefault();
event.stopPropagation();
this.setState({openModal: true});
}
/**
* The method to update the history.
* to: The URL to route.
* */
handleHistory(to) {
this.props.history.push(to);
}
logout(event, index, value) {
AuthHandler.logout();
}
closeModal() {
this.setState({openModal: false});
}
render() {
return (
<Container noGutters fluid id="container">
<div id="header-content">
<div id="header">
<span id="header-text">
WSO2 IoT App Store
</span>
<div id="header-btn-container">
<i className="fw fw-notification btn-header"></i>
<i className="fw fw-user btn-header"></i>
</div>
<div id="search-box">
<i className="fw fw-search search-icon">
</i>
<Input
id="search"
name="search"
placeholder={'Search for Applications'}
onChange={(event) => console.log(event.target.value)} //TODO: Remove this
/>
</div>
</div>
<div id="add-btn-container">
<FloatingButton
className="add-btn small"
onClick={this.handleApplicationCreateClick.bind(this)}
/>
</div>
</div>
<div id="application-content" style={this.state.style}>
<Row>
<Col>
{this.props.children}
</Col>
</Row>
</div>
</Container>
);
}
}
BaseLayout.propTypes = {
children: PropTypes.element
};
export default withRouter(BaseLayout);

@ -0,0 +1,301 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {Button, Col, Row, Table} from 'reactstrap';
import Drawer from '../UIComponents/Drawer/Drawer';
import ApplicationView from './View/ApplicationView';
import Configuration from '../../common/configuration';
import Constants from '../../common/constants';
/**
* The App Create Component.
*
* Application creation is handled through a Wizard. (We use Material UI Stepper.)
*
* In each step, data will be set to the state separately.
* When the wizard is completed, data will be arranged and sent to the api.
* */
class ApplicationListing extends Component {
constructor() {
super();
this.searchApplications = this.searchApplications.bind(this);
this.onRowClick = this.onRowClick.bind(this);
this.setData = this.setData.bind(this);
this.sortData = this.sortData.bind(this);
this.compare = this.compare.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
this.state = {
searchedApplications: [],
applications: [],
asc: true,
open: false,
application: {},
drawer: {},
appListStyle: {},
//TODO: Remove this declaration.
image: [{id: "1", src: "https://www.greenfoot.org/images/logos/macos.png"},
{
id: "2",
src: "http://dl1.cbsistatic.com/i/r/2016/08/08/0e67e43a-5a45-41ab-b81d-acfba8708044/resize/736x552/0c0ee669677b5060a0fa1bfb0c7873b4/android-logo-promo-470.png"
}]
};
}
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "applicationName",
data_type: "string",
sortable: true,
label: "Application Name",
sort: this.sortData
},
{
data_id: "platform",
data_type: "image_array",
sortable: false,
label: "Platform"
},
{
data_id: "category",
data_type: "string",
sortable: false,
label: "Category"
},
{
data_id: "status",
data_type: "string",
sortable: false,
label: "Status"
},
{
data_id: "edit",
data_type: "button",
sortable: false,
label: ""
}
];
applications = [
{
id: "3242342ffww3423",
applicationName: "Facebook",
platform: "android",
category: "Business",
status: "Published"
},
{
icon: "http://dl1.cbsistatic.com/i/r/2016/08/08/0e67e43a-5a45-41ab-b81d-acfba8708044/resize/736x552/0c0ee669677b5060a0fa1bfb0c7873b4/android-logo-promo-470.png",
id: "324234233423423",
applicationName: "Twitter",
platform: "android",
category: "Business",
status: "Created"
},
{
icon: "https://www.greenfoot.org/images/logos/macos.png",
id: "3242d3423423423",
applicationName: "Massenger",
platform: "android",
category: "Business",
status: "In Review"
}
];
componentWillMount() {
// let getApps = ApplicationMgtApi.getApplications();
// getApps.then(response => {
// let apps = this.setData(response.data.applications);
// console.log(apps); //TODO: Remove this.
// this.setState({searchedApplications: apps});
// // console.log(this.setState({data: response.data}), console.log(this.state));
// }).catch(err => {
// AuthHandler.unauthorizedErrorHandler(err);
// });
}
/**
* Extract application from application list and update the state.
* */
setData(applications) {
let apps = [];
for (let app in applications) {
let application = {};
application.id = applications[app].uuid;
application.applicationName = applications[app].name;
application.platform = applications[app].platform.name;
application.category = applications[app].category.id;
application.status = applications[app].currentLifecycle.lifecycleState.name;
apps.push(application);
}
this.setState({searchedApplications: apps});
}
/**
* Handles the search action.
* When typing in the search bar, this method will be invoked.
* @param event: The event triggered from typing in the search box.
* @param searchText: The text that typed in the search box.
* */
searchApplications(event, searchText) {
let searchedData;
if (searchText) {
searchedData = this.state.applications.filter((dataItem) => {
return dataItem.applicationName.includes(searchText);
});
} else {
searchedData = this.state.applications;
}
//TODO: Remove the console log.
this.setState({searchedApplications: searchedData}, console.log("Searched data ", this.state.searchedApplications));
}
/**
* Handles sort data function and toggles the asc state.
* asc: true : sort in ascending order.
* */
sortData() {
console.log(this.state);
let isAsc = this.state.asc;
let sortedData = isAsc ? this.state.searchedApplications.sort(this.compare) : this.data.reverse();
this.setState({searchedApplications: sortedData, asc: !isAsc});
}
compare(a, b) {
if (a.applicationName < b.applicationName)
return -1;
if (a.applicationName > b.applicationName)
return 1;
return 0;
}
onRowClick() {
let style = {
width: '500px',
marginLeft: '500px'
};
let appListStyle = {
marginRight: '500px'
};
this.setState({drawer: style, appListStyle: appListStyle});
}
handleButtonClick() {
console.log("Application Listing");
this.props.history.push("apps/edit/fdsfdsf343");
}
remove(imageId) {
let tmp = this.state.image;
console.log(imageId);
let rem = tmp.filter((image) => {
return image.id !== imageId
});
this.setState({image: rem});
}
closeDrawer() {
let style = {
width: '0',
marginLeft: '0'
};
let appListStyle = {
marginRight: '0',
};
this.setState({drawer: style, appListStyle: appListStyle});
}
render() {
return (
<div id="application-list" style={this.state.appListStyle}>
<Row>
<Col>
<Table striped hover>
<thead>
<tr>
<th></th>
{/* TODO: Remove console.log and add sort method. */}
<th onClick={() => {
console.log("sort")
}}>Application Name
</th>
<th>Category</th>
<th>Platform</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{this.applications.map(
(application) => {
return (
<tr key={application.id} onClick={this.onRowClick}>
<td>
{/* TODO: Move this styles to css. */}
<img
src={application.icon}
height='50px'
width='50px'
style={{border: 'solid 1px black', borderRadius: "100%"}}
/>
</td>
<td>{application.applicationName}</td>
<td>{application.category}</td>
<td>{application.platform}</td>
<td>{application.status}</td>
<td>
<Button onClick={this.handleButtonClick}>
<i className="fw fw-edit"></i>
</Button>
</td>
</tr>
)
}
)}
</tbody>
</Table>
</Col>
</Row>
<Drawer onClose={this.closeDrawer.bind(this)} style={this.state.drawer}>
<ApplicationView/>
</Drawer>
</div>
);
}
}
ApplicationListing.propTypes = {};
export default withRouter(ApplicationListing);

@ -0,0 +1,129 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {Col, Row} from "reactstrap";
/**
* Application view component.
* Shows the details of the application.
* */
class ApplicationView extends Component {
constructor() {
super();
this.state = {
application: {}
}
}
componentWillReceiveProps(props, nextProps) {
this.setState({application: props.application});
console.log(props.application, nextProps)
}
componentDidMount() {
//TODO: Download image artifacts.
}
handleEdit() {
this.props.history.push("/assets/apps/edit/" + this.state.application.uuid);
}
render() {
const platform = this.state.application;
console.log(platform);
return (
<div id="application-view-content">
<div id="application-view-row">
<Row>
<Col>
<div id="app-icon">
</div>
</Col>
<Col>
<Row>
<span><strong>Facebook</strong></span>
</Row>
<Row>
<span className="app-updated-date">Last updated on 2017-09-23</span>
</Row>
</Col>
</Row>
</div>
<div id="application-view-row">
<Row>
<Col>
<span className="app-install-count">2k Installs</span>
</Col>
</Row>
<Row>
<Col>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
</Col>
<Col>
<a href="#">View in Store</a>
</Col>
</Row>
</div>
<hr/>
<div id="application-view-row">
<Row>
<Col>
<span><strong>Description: </strong></span>
</Col>
<Col>
<p>sdfjlkdsjfsjdfjsdf sfjdslkjfdsflkjdsfslkdjfl j</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Tags: </strong></span>
</Col>
<Col>
<p>[list of tags...]</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Release: </strong></span>
</Col>
<Col>
<p>Production</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Version: </strong></span>
</Col>
<Col>
<p>v1.0</p>
</Col>
</Row>
</div>
</div>
);
}
}
export default withRouter(ApplicationView);

@ -1,132 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import Badge from 'material-ui/Badge';
import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import Drawer from 'material-ui/Drawer';
import IconButton from 'material-ui/IconButton';
import {List, ListItem} from 'material-ui/List';
import Apps from 'material-ui/svg-icons/navigation/apps';
import NotificationsIcon from 'material-ui/svg-icons/social/notifications';
import ActionAccountCircle from 'material-ui/svg-icons/action/account-circle';
import {Link, withRouter} from 'react-router-dom';
/**
* Base Layout:
* App bar
* Left Navigation
* Middle content.
* */
class BaseLayout extends Component {
handleApplicationClick() {
this.handleHistory('/assets/apps');
}
/**
* The method to update the history.
* to: The URL to route.
* */
handleHistory(to) {
this.props.history.push(to);
}
handleUserLogin() {
if (this.props.state.store.user) {
return (
<IconButton tooltip={this.props.state.store.user}>
<ActionAccountCircle/>
</IconButton>
);
} else {
return (
<Link to='/login'> Login</Link>
);
}
}
handleNotification() {
if (this.props.state.store.user) {
return (
<Badge
badgeContent={this.props.state.store.notifications}
secondary={true}
badgeStyle={{top: 12, right: 12}}>
<IconButton tooltip="Notifications">
<NotificationsIcon/>
</IconButton>
</Badge>
);
}
}
render() {
return (
<div>
<AppBar title="App Store"
iconElementRight={
<div>
{this.handleNotification()}
{this.handleUserLogin()}
</div>
}
/>
<div>
<Drawer containerStyle={{height: 'calc(100% - 64px)', width: '15%', top: '13%', left: '1%'}}
open={true}>
<List>
<ListItem primaryText="Applications"
leftIcon={<Apps/>}
initiallyOpen={false}
primaryTogglesNestedList={true}
onClick={this.handleApplicationClick.bind(this)}
nestedItems={[
<ListItem
key={1}
primaryText="Business" //TODO: categoryies ...
leftIcon={<List/>}
/>]}
/>
</List>
</Drawer>
</div>
<div style=
{
{
height: 'calc(100% - 64px)',
marginLeft: '16%',
width: 'calc(100%-15%)',
top: 64,
left: "-100px"
}
}>
{this.props.children}
</div>
</div>);
}
}
BaseLayout.propTypes = {
children: PropTypes.element
};
export default withRouter(BaseLayout);

@ -1,168 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import qs from 'qs';
import React, {Component} from 'react';
import Checkbox from 'material-ui/Checkbox';
import {Redirect, Route} from 'react-router-dom';
import RaisedButton from 'material-ui/RaisedButton';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import {TextValidator, ValidatorForm} from 'react-material-ui-form-validator';
import Store from '../App';
//todo: remove the {TextValidator, ValidatorForm} and implement it manually.
/**
* The Login Component.
*
* This component contains the Login form and methods to handle field change events.
* The user name and password will be set to the state and sent to the api.
*
* If the user is already logged in, it will redirect to the last point where the user was.
* */
class Login extends Component {
constructor() {
super();
this.state = {
isLoggedIn: false,
referrer: "/",
userName: "",
rememberMe: true
}
}
componentDidMount() {
let queryString = this.props.location.search;
console.log(queryString);
queryString = queryString.replace(/^\?/, '');
/* With QS version up we can directly use {ignoreQueryPrefix: true} option */
let params = qs.parse(queryString);
if (params.referrer) {
this.setState({referrer: params.referrer});
}
}
handleLogin(event) {
event.preventDefault();
console.log(this.props);
//TODO: send authentication request.
let location = {
pathname: this.state.referrer
};
let storeState = {
store : {
user: this.state.userName,
notifications: 0
}
};
this.props.updateState(storeState);
this.props.history.push(location);
}
/**
* Handles the username field change event.
* */
onUserNameChange(event) {
this.setState(
{
userName: event.target.value
}
);
}
/**
* Handles the password field change event.
* */
onPasswordChange(event) {
this.setState(
{
password: event.target.value
}
);
}
/**
* Handles the remember me check.
* */
handleRememberMe() {
this.setState(
{
rememberMe: !this.state.rememberMe
}
);
}
handleSuccessfulLogin() {
return (
<Redirect to='/store'/>
);
}
render() {
if (!(this.state.isLoggedIn && this.state.userName)) {
return (
<div>
{/*TODO: Style the components.*/}
<Card>
<CardTitle title="WSO2 IoT App Store"/>
<CardActions>
<ValidatorForm
ref="form"
onSubmit={this.handleLogin.bind(this)}
onError={errors => console.log(errors)}>
<TextValidator
floatingLabelText="User Name"
floatingLabelFixed={true}
onChange={this.onUserNameChange.bind(this)}
name="userName"
validators={['required']}
errorMessages={['User Name is required']}
value={this.state.userName}
/>
<br/>
<TextValidator
floatingLabelText="Password"
floatingLabelFixed={true}
onChange={this.onPasswordChange.bind(this)}
name="password"
type="password"
value={this.state.password}
validators={['required']}
errorMessages={['Password is required']}
/>
<br/>
<Checkbox label="Remember me."
onCheck={this.handleRememberMe.bind(this)}
checked={this.state.rememberMe}/>
<br/>
<RaisedButton type="submit" label="Login"/>
</ValidatorForm>
</CardActions>
</Card>
</div>);
} else {
this.handleSuccessfulLogin();
}
}
}
export default Login;

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
/**
* Review Listing.
* */
class ReviewListing extends Component {
constructor() {
super();
}
render() {
return (
<div>
Reviews List
</div>
);
}
}
export default ReviewListing;

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
/**
* Review details view.
* */
class ReviewView extends Component {
constructor() {
super();
}
render() {
return (
<div>
Review
</div>
);
}
}
export default ReviewView;

@ -0,0 +1,75 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
/**
* Component for holding uploaded image.
* This component has the feature to remove selected image from the array.
* */
class AppImage extends Component {
constructor() {
super();
this.removeImage = this.removeImage.bind(this);
this.scriptId = "appImage";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Triggers the parent method to remove the selected image.
* @param event: The click event of the component.
* */
removeImage(event) {
event.preventDefault();
this.props.onRemove(event.target.id);
}
render() {
const {image, imageId} = this.props;
return (
<div className="image-container" style={this.props.imageStyles}>
<img src={image} className="image" id={imageId}/>
<div className="btn-content">
<i className="close-btn" id={imageId} onClick={this.removeImage}>X</i>
</div>
</div>
)
}
}
AppImage.propTypes = {
image: PropTypes.string,
imageId: PropTypes.string,
onRemove: PropTypes.func,
imageStyles: PropTypes.object
};
export default AppImage;

@ -0,0 +1,50 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import Theme from '../../../theme';
class Chip extends Component {
constructor() {
super();
this.scriptId = "chip";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div className="chip">
{this.props.image?<img src={this.props.image} alt="Person" width="96" height="96" />:<div/>}
{this.props.text}
<span className="close-btn" >&times;</span>
</div>
)
}
}
export default Chip;

@ -0,0 +1,153 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import DataTableRow from './DataTableRow';
import DataTableHeader from './DataTableHeader';
import RaisedButton from 'material-ui/RaisedButton';
import {Table, TableBody, TableHeader, TableRow} from 'material-ui/Table';
/**
* The Custom Table Component.
* This component wraps the material-ui Table component and add some extra functionalities.
* 1. Table header click. (For sorting)
* 2. Table row click.
*
* The main sort function is defined in the component where the data table is created and passed to the
* DataTable component via props.
*
* Following are the DataTable proptypes.
* 1. Headers: Table headers. This is an array of Json Objects.
* An Header Object contains the properties of each header. Currently following properties
* are supported.
* * sortable: boolean : whether the table column is sortable or not.
* * sort: func : If sortable, the sort function.
* * sort: func : If sortable, the sort function.
* * sort: func : If sortable, the sort function.
* * label: String: The Table header string.
* * id: String: Unique id for header.
*
* 2. Data: The list of data that needs to be displayed in the table.
* This is also a json array of data objects.
* The Json object should contain key: value pair where the key is the header id.
*
* */
class DataTable extends Component {
constructor() {
super();
this.handleRowClick = this.handleRowClick.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = {
data: [],
headers: [],
};
this.scriptId = "data-table"
};
componentWillMount() {
console.log("Will mount", this.props.data); //TODO: Remove this
this.setState({data: this.props.data, headers: this.props.headers}, Theme.insertThemingScripts(this.scriptId));
/**
*Loading the theme files based on the the user-preference.
*/
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
shouldComponentUpdate(nextProps, nextState) {
if (!nextProps.data) {
this.setState({data: nextState.data});
return true;
}
this.setState({data: nextProps.data});
return true;
}
/**
* Triggers when user click on table row.
* This method invokes the parent method handleRowClick, which is passed via props.
* */
handleRowClick(id) {
this.props.handleRowClick(id);
}
handleBtnClick(id) {
this.props.handleButtonClick(id);
}
render() {
const {data, headers} = this.state;
//TODO: Remove this
console.log(data);
let noDataContent = null;
if (this.props.noDataMessage.type === 'button') {
noDataContent = <div><RaisedButton label={this.props.noDataMessage.text}/></div>
}
if (data) {
return (<Table
selectable={false}>
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
<TableRow>
{headers.map((header) => {
return (
<DataTableHeader
key={header.data_id}
className="datatableRowColumn"
header={header}
/>
)}
)}
</TableRow>
</TableHeader>
<TableBody>
{data.map((dataItem) => {
return (
<DataTableRow
key={dataItem.id}
dataItem={dataItem}
handleButtonClick={this.handleBtnClick}
handleClick={this.handleRowClick}
/>
)
})}
</TableBody>
</Table>)
}
return (<div>{noDataContent}</div>);
}
}
DataTable.prototypes = {
data: PropTypes.arrayOf(Object),
headers: PropTypes.arrayOf(Object),
sortData: PropTypes.func,
handleRowClick: PropTypes.func,
noDataMessage: PropTypes.object
};
export default DataTable;

@ -0,0 +1,87 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import FlatButton from 'material-ui/FlatButton';
import {TableHeaderColumn} from 'material-ui/Table';
/**
* Data Table header component.
* This component creates the header elements of the table.
* */
class DataTableHeader extends Component {
constructor() {
super();
this.tableHeaderClick = this.tableHeaderClick.bind(this);
this.scriptId = "data-table";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* The onClick function of the table header.
* Invokes the function passed in the header object.
* */
tableHeaderClick() {
this.props.header.sort();
}
render() {
let headerCell = null;
/**
* If the header is sortable, create a button with onClick handler.
* else create a span element with label as the table header.
* */
if (this.props.header.sortable) {
headerCell =
<FlatButton
label={this.props.header.label}
onClick={this.tableHeaderClick}
className="sortableHeaderCell"
/>
} else {
headerCell = <span className="notsortableHeaderCell">{this.props.header.label}</span>;
}
return (
<TableHeaderColumn key={this.props.header.id} className="datatableHeaderColumn">
{headerCell}
</TableHeaderColumn>
);
}
}
DataTableHeader.prototypes = {
header: PropTypes.object
};
export default DataTableHeader;

@ -0,0 +1,112 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import IconButton from 'material-ui/IconButton';
import Create from 'material-ui/svg-icons/content/create'
import {TableRow, TableRowColumn} from 'material-ui/Table';
import Avatar from 'material-ui/Avatar';
/**
* Data table row component.
* This component created a row in the data table according to the props.
* */
class DataTableRow extends Component {
constructor() {
super();
this.state = {
dataItem: {}
};
this.scriptId = "data-table";
}
componentWillMount() {
this.setState({dataItem: this.props.dataItem});
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Triggers the click event on the data table row.
* */
handleClick(event) {
event.stopPropagation();
this.props.handleClick(this.state.dataItem.id);
}
handleBtnClick(event) {
event.stopPropagation();
console.log(event.target['id'])
this.props.handleButtonClick(event.target['id']);
}
render() {
const {dataItem} = this.state;
return (
<TableRow
key={this.props.key}
onClick={this.handleClick.bind(this)}
>
<TableRowColumn
className="datatableRowColumn"
key={Math.random()}
>
<Avatar>{dataItem.name}</Avatar>
</TableRowColumn>
{Object.keys(dataItem).map((key) => {
if (key !== 'id') {
return (
<TableRowColumn
className="datatableRowColumn"
key={key}
>
{dataItem[key]}
</TableRowColumn>)
}
})}
<TableRowColumn
className="datatableRowColumn"
key={dataItem.id}
>
<IconButton id={dataItem.id} onClick={this.handleBtnClick.bind(this)}>
<Create id={dataItem.id}/>
</IconButton>
</TableRowColumn>
</TableRow>
);
}
}
DataTableRow.propTypes = {
onClick: PropTypes.func,
data: PropTypes.object
};
export default DataTableRow;

@ -0,0 +1,70 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme'
/**
* Custom React component for Application View.
* */
class Drawer extends Component {
constructor() {
super();
this.closeDrawer = this.closeDrawer.bind(this);
this.scriptId = "drawer";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Closes the drawer.
* */
closeDrawer() {
this.props.onClose();
}
render() {
return (
<div>
<div id="app-view" className="app-view-drawer" style={this.props.style}>
<a onClick={this.closeDrawer} className="drawer-close-btn"><i className="fw fw-uncheck"></i></a>
{this.props.children}
</div>
</div>
);
}
}
Drawer.propTypes = {
style: PropTypes.object,
children: PropTypes.node,
onClose: PropTypes.func
};
export default Drawer;

@ -0,0 +1,63 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
/**
* Floating Action button.
* */
class FloatingButton extends Component {
constructor() {
super();
this.scriptId = "floatingButton";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
handleClick(event) {
this.props.onClick(event);
}
render() {
let classes = 'btn-circle ' + this.props.className;
return (
<div className={classes} onClick={this.handleClick.bind(this)}>
<i className="fw fw-add"></i>
</div>
)
}
}
FloatingButton.propTypes = {
classNames: PropTypes.string,
onClick: PropTypes.func
};
export default FloatingButton;

@ -0,0 +1,88 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Dropzone from "react-dropzone";
import {Row} from "reactstrap";
import Theme from '../../../theme';
class ImageUploader extends Component {
constructor() {
super();
this.setImages = this.setImages.bind(this);
this.state = {
images: []
};
this.scriptId = "imageUploader";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
setImages(images) {
this.props.setImages(images);
}
render() {
let {images, height, width, accepted, multiple, maxAmount} = this.props;
return (
<div id="screenshot-container">
<Row>
{images.map((tile) => (
<input type="image" src={tile[0].preview} onClick=""/>
)
)}
</Row>
{this.state.screenshots.length < maxAmount ?
<Dropzone
className="add-image"
accept="image/jpeg, image/png"
onDrop={(accepted, rejected) => {
this.setImages(accepted);
}}
>
<p className="add-image-symbol">+</p>
</Dropzone> : <div/>}
</div>
);
}
}
ImageUploader.prototypes = {
height: PropTypes.string,
width: PropTypes.string,
accepted: PropTypes.array,
multiple: PropTypes.bool,
maxAmount: PropTypes.number,
setImages: PropTypes.func
};
export default ImageUploader;

@ -0,0 +1,58 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Col, Row} from "reactstrap";
import Theme from '../../../theme'
class NotificationItem extends Component {
constructor() {
super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div>
<Row>
<Col>
<div className="notification-app-icon small">
<img/>
</div>
</Col>
<Col>
<p>Your application, Facebook has been published.</p>
</Col>
</Row>
</div>
);
}
}
export default NotificationItem;

@ -0,0 +1,84 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Col, Row} from "reactstrap";
import './notification.css';
import Theme from '../../../theme'
class NotificationView extends Component {
constructor() {
super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div id="notification-view-content">
<div>
<Row id="notification-content">
<Col xs="3">
<div className="notification-app-icon medium">
</div>
</Col>
<Col xs="9">
<Row>
<span><strong>Application Name</strong></span>
</Row>
<Row>
<span>Version 1.0</span>
</Row>
<Row>
<p id="app-reject-msg">Your Application was rejected</p>
</Row>
</Col>
</Row>
<hr/>
<Row id="notification-content">
<Col xs="12">
<p>Following validations were detected in your review submission.
Please attend to them and re-submit</p>
<ul>
<li>sdjjfsdfsdfkjs shdfjhlkds hflkhfdslkd </li>
<li>sdfkds jfdsljfklsdfjksdjlksdjdlkf</li>
<li>sfksdf slkjskd jfjds lkfjdsfdsfdslkf sjf lkdsf</li>
<li>skfjslkdjfsdjfjksldjf sdkl jflkds jfkslfjs</li>
<li>ksdf jks;kshflk hlkjhds lkjhdsklhsd lkf</li>
<li> jsdljflksd jfklsdfskljfkjshf;ks ldj</li>
</ul>
</Col>
</Row>
</div>
</div>
);
}
}
export default NotificationView;

@ -0,0 +1,51 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import Theme from '../../../theme';
class Switch extends Component {
constructor() {
super();
this.scriptId = "switch";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
const {height, width} = this.props;
return (
<label className="switch">
<input type="checkbox" onChange={this.props.onChange}/>
<span className="slider round"></span>
</label>
)
}
}
export default Switch;

@ -0,0 +1,171 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import qs from 'qs';
import React, {Component} from 'react';
import {Redirect, Switch} from 'react-router-dom';
import AuthHandler from '../../../api/authHandler';
import {Button, Card, CardBlock, CardTitle, Col, Form, FormGroup, Input, Label} from 'reactstrap';
/**
* The Login Component.
*
* This component contains the Login form and methods to handle field change events.
* The user name and password will be set to the state and sent to the api.
*
* If the user is already logged in, it will redirect to the last point where the user was.
* */
class Login extends Component {
constructor() {
super();
this.state = {
isLoggedIn: false,
referrer: "/",
userName: "",
password: "",
rememberMe: true,
errors: {}
}
}
componentDidMount() {
let queryString = this.props.location.search;
console.log(queryString);
queryString = queryString.replace(/^\?/, '');
/* With QS version up we can directly use {ignoreQueryPrefix: true} option */
let params = qs.parse(queryString);
if (params.referrer) {
this.setState({referrer: params.referrer});
}
}
handleLogin(event) {
event.preventDefault();
this.validateForm();
}
/**
* Handles the username field change event.
* */
onUserNameChange(event, value) {
console.log(event.target.value);
this.setState(
{
userName: event.target.value
}
);
}
/**
* Handles the password field change event.
* */
onPasswordChange(event, value) {
this.setState(
{
password: event.target.value
}
);
}
/**
* Handles the remember me check.
* */
handleRememberMe() {
this.setState(
{
rememberMe: !this.state.rememberMe
}
);
}
/**
* Validate the login form.
* */
validateForm() {
let errors = {};
let validationFailed = true;
if (!this.state.password) {
errors["passwordError"] = "Password is Required";
validationFailed = true;
} else {
validationFailed = false;
}
if (!this.state.userName) {
errors["userNameError"] = "User Name is Required";
validationFailed = true;
} else {
validationFailed = false;
}
if (validationFailed) {
this.setState({errors: errors}, console.log(errors));
} else {
let loginPromis = AuthHandler.login(this.state.userName, this.state.password);
loginPromis.then(response => {
console.log(AuthHandler.getUser());
this.setState({isLoggedIn: AuthHandler.getUser()});
})
}
}
render() {
if (!this.state.isLoggedIn) {
return (
<div id="login-container">
{/*TODO: Style the components.*/}
<Card id="login-card">
<CardBlock>
<CardTitle>WSO2 IoT APP Store</CardTitle>
<Form onSubmit={this.handleLogin.bind(this)}>
<FormGroup row>
<Label for="userName" sm={2}>User Name:</Label>
<Col sm={10}>
<Input type="text" name="userName" id="userName" placeholder="User Name"
onChange={this.onUserNameChange.bind(this)}/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="password" sm={2}>Password:</Label>
<Col sm={10}>
<Input type="password" name="text" id="password" placeholder="Password"
onChange={this.onPasswordChange.bind(this)}/>
</Col>
</FormGroup>
<FormGroup check row>
<Col sm={{size: 10, offset: 2}}>
<Button type="submit" id="login-btn">Login</Button>
</Col>
</FormGroup>
</Form>
</CardBlock>
</Card>
</div>);
} else {
return (
<Switch>
<Redirect to={this.state.referrer}/>
</Switch>
);
}
}
}
export default Login;

@ -16,10 +16,13 @@
* under the License. * under the License.
*/ */
import Login from './User/Login/Login';
import NotFound from './Error/NotFound';
import BaseLayout from './AppStoreBase/BaseLayout';
import ApplicationListing from './Application/ApplicationListing';
import BaseLayout from './BaseLayout';
/** /**
* Contains all UI components related to Application, Login and Platform * Contains all UI components related to Application, Login and Platform
*/ */
export default {BaseLayout}; export {Login, BaseLayout, ApplicationListing, NotFound};

@ -1,7 +0,0 @@
{
"theme" : {
"current" : "default",
"default" : "lightBaseTheme",
"custom" : "custom-theme"
}
}

@ -1,6 +1,63 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import Store from './App'; import Store from './App';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import registerServiceWorker from './registerServiceWorker';
import {IntlProvider, addLocaleData, defineMessages} from 'react-intl';
import Axios from 'axios';
import Constants from './common/constants';
import Configuration from './common/configuration';
function loadStore() {
const possibleLocale = navigator.language.split("-")[0];
let loadLocaleFile = Axios.create({
baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales/"
+ possibleLocale + ".json"
}).get();
/**
* This is the base js file of the app. All the content will be rendered in the root element.
* */
loadLocaleFile.then(response => {
const messages = defineMessages(response.data);
addLocaleData(require('react-intl/locale-data/' + possibleLocale));
ReactDOM.render(<IntlProvider locale={possibleLocale}
messages={messages}><Store/></IntlProvider>, document.getElementById('root'));
registerServiceWorker();
}).catch(error => {
addLocaleData(require('react-intl/locale-data/en'));
let defaultLocale = Axios.create({
baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales"
+ Constants.defaultLocale + ".json"
}).get();
defaultLocale.then(response => {
const messages = defineMessages(response.data);
ReactDOM.render(<IntlProvider locale={possibleLocale}
messages={messages}><Store/></IntlProvider>, document.getElementById('root'));
registerServiceWorker();
}).catch(error => {
});
});
}
ReactDOM.render(<Store />, document.getElementById('root')); Configuration.loadConfiguration(loadStore);

@ -0,0 +1,126 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/store/service-worker.js`;
if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl);
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

@ -0,0 +1,110 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import axios from 'axios';
/**
* This class will read through the configuration file and saves the theme names for the usage in other files.
* User can define the themes in the config.json. The themes will be loaded based on the user preference.
*/
class Theme {
constructor() {
this.defaultThemeType = "default";
this.currentThemeType = this.defaultThemeType;
this.currentTheme = "lightBaseTheme";
this.themeFolder = "themes";
this.styleSheetType = "text/css";
this.styleSheetRel = "stylesheet";
this.selectedTheme = this.defaultThemeType;
//TODO Need to get the app context properly when the server is ready
this.baseURL = window.location.origin;
this.appContext = window.location.pathname.split("/")[1];
this.loadThemeConfigs = this.loadThemeConfigs.bind(this);
this.loadThemeFiles = this.loadThemeFiles.bind(this);
this.insertThemingScripts = this.insertThemingScripts.bind(this);
this.removeThemingScripts = this.removeThemingScripts.bind(this);
}
/**
* To load the theme related configurations from the configuration file.
* @returns the http response.
*/
loadThemeConfigs () {
let httpClient = axios.create({
baseURL: this.baseURL + "/" + this.appContext + "/config.json",
timeout: 2000
});
httpClient.defaults.headers.post['Content-Type'] = 'application/json';
return httpClient.get();
}
/**
* To load the particular theme file from the path.
* @param path Path to load the theme files
* @returns Http response from the particular file.
*/
loadThemeFiles (path) {
let httpClient = axios.create({
baseURL: this.baseURL + "/" + this.appContext + path,
timeout: 2000
});
return httpClient.get();
}
/**
* To insert the css files to the document.
* @param scriptId ID of the script that need to be inserted
*/
insertThemingScripts(scriptId) {
const script = scriptId + ".css";
let themePath = "/" + this.themeFolder + "/" + this.selectedTheme + "/" + script;
let themeFile = this.loadThemeFiles(themePath);
let head = document.getElementsByTagName("head")[0];
let link = document.createElement("link");
link.type = this.styleSheetType;
link.href = this.baseURL + "/" + this.appContext + themePath;
link.id = scriptId;
link.rel = this.styleSheetRel;
this.removeThemingScripts(scriptId);
themeFile.then(function () {
head.appendChild(link);
}).catch(error => {
// If there is no customized css file, load the default one.
themePath = "/" + this.themeFolder + "/" + this.defaultThemeType + "/" + script;
link.href = this.baseURL + "/" + this.appContext + themePath;
head.appendChild(link);
});
}
/**
* To remove the css scripts that are inserted before.
* @param scriptId Id of the script that need to be removed
*/
removeThemingScripts(scriptId) {
let styleSheet = document.getElementById(scriptId);
if (styleSheet !== null) {
styleSheet.disabled = true;
styleSheet.parentNode.removeChild(styleSheet);
}
}
}
export default (new Theme);

@ -15,22 +15,15 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
var webpack = require('webpack');
var path = require('path'); var path = require('path');
var BUILD_DIR = path.resolve(__dirname, 'public/dist');
var APP_DIR = path.resolve(__dirname, 'src');
const config = { const config = {
entry: { entry: {
index: APP_DIR+ '/index.js' index: './src/index.js'
}, },
output: { output: {
path: path.resolve(__dirname, BUILD_DIR), path: path.resolve(__dirname, 'public/dist'),
filename: 'index.js' filename: '[name].js'
},
resolve: {
extensions: ['*', '.jsx', '.js']
}, },
devtool: "source-map", devtool: "source-map",
plugins: [], plugins: [],
@ -39,7 +32,6 @@ const config = {
rules: [ rules: [
{ {
test: /\.(js|jsx)$/, test: /\.(js|jsx)$/,
include: APP_DIR,
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [
{ {
@ -53,7 +45,11 @@ const config = {
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: ['style-loader', 'css-loader'] use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.scss$/,
use: [ 'style-loader', 'scss-loader' ]
}, },
{ {
test: /\.less$/, test: /\.less$/,
@ -66,7 +62,16 @@ const config = {
}] }]
} }
] ]
},
resolve: {
// you can now require('file') instead of require('file.coffee')
extensions: ['.jsx', '.js', '.ttf', '.woff', '.woff2', '.svg']
} }
}; };
if (process.env.NODE_ENV === "development") {
config.watch = true;
}
module.exports = config; module.exports = config;

Loading…
Cancel
Save