Merge pull request #1022 from menakaj/application-mgt

Publisher UI improvements.
feature/appm-store/pbac
sinthuja 7 years ago committed by GitHub
commit 9eb25444a7

2
.gitignore vendored

@ -26,3 +26,5 @@ target
hs_err_pid*
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/node_modules/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/build/
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/package-lock.json
components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/src/main/resources/publisher/npm-debug.log

@ -22,7 +22,29 @@ package org.wso2.carbon.device.application.mgt.auth.handler.util;
public class Constants {
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:roles:view perm:devices:view perm:platform:get";
"perm:roles:view perm:devices:view perm:platform:get perm:admin:devices:view perm:roles:add " +
"perm:roles:add-users perm:roles:update perm:roles:permissions perm:roles:details perm:roles:view" +
" perm:roles:create-combined-role perm:roles:delete perm:dashboard:vulnerabilities " +
"perm:dashboard:non-compliant-count perm:dashboard:non-compliant perm:dashboard:by-groups " +
"perm:dashboard:device-counts perm:dashboard:feature-non-compliant perm:dashboard:count-overview " +
"perm:dashboard:filtered-count perm:dashboard:details perm:get-activity perm:devices:delete " +
"perm:devices:applications perm:devices:effective-policy perm:devices:compliance-data " +
"perm:devices:features perm:devices:operations perm:devices:search perm:devices:details " +
"perm:devices:update perm:devices:view perm:view-configuration perm:manage-configuration " +
"perm:policies:remove perm:policies:priorities perm:policies:deactivate perm:policies:get-policy-details" +
" perm:policies:manage perm:policies:activate perm:policies:update perm:policies:changes " +
"perm:policies:get-details perm:users:add perm:users:details perm:users:count perm:users:delete " +
"perm:users:roles perm:users:user-details perm:users:credentials perm:users:search perm:users:is-exist " +
"perm:users:update perm:users:send-invitation perm:admin-users:view perm:groups:devices perm:groups:update " +
"perm:groups:add perm:groups:device perm:groups:devices-count perm:groups:remove perm:groups:groups " +
"perm:groups:groups-view perm:groups:share perm:groups:count perm:groups:roles perm:groups:devices-remove " +
"perm:groups:devices-add perm:groups:assign perm:device-types:features perm:device-types:types " +
"perm:applications:install perm:applications:uninstall perm:admin-groups:count perm:admin-groups:view" +
" perm:notifications:mark-checked perm:notifications:view perm:admin:certificates:delete " +
"perm:admin:certificates:details perm:admin:certificates:view perm:admin:certificates:add " +
"perm:admin:certificates:verify perm:admin perm:devicetype:deployment perm:device-types:events " +
"perm:device-types:events:view perm:admin:device-type perm:device:enroll perm:geo-service:analytics-view " +
"perm:geo-service:alerts-manage";
public static final String[] TAGS = {"device_management"};
public static final String USER_NAME = "userName";

@ -10,18 +10,18 @@
"license": "Apache License 2.0",
"dependencies": {
"axios": "^0.16.2",
"bootstrap": "^4.0.0-alpha.6",
"bootstrap": "^4.0.0-beta",
"flux": "^3.1.3",
"history": "^4.7.2",
"latest-version": "^3.1.0",
"material-ui": "^0.19.1",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"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-dropzone": "^4.1.2",
"material-ui": "^0.19.3",
"prop-types": "^15.6.0",
"qs": "^6.5.1",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.2",
"react-addons-transition-group": "^15.6.2",
"react-dom": "^15.6.2",
"react-dropzone": "^4.1.3",
"react-images-uploader": "^1.1.0",
"react-material-ui-form-validator": "^0.5.1",
"react-modal": "^2.3.2",
@ -43,8 +43,8 @@
"css-loader": "^0.28.7",
"less": "^2.7.2",
"less-loader": "^4.0.4",
"mocha": "^3.4.1",
"mock-local-storage": "^1.0.2",
"mocha": "^3.5.3",
"mock-local-storage": "^1.0.5",
"node-sass": "^4.5.3",
"react-intl": "^2.4.0",
"sass-loader": "^6.0.6",

@ -23,6 +23,7 @@
<meta name="theme-color" content="#000000">
<link rel="stylesheet" type="text/css" href="/publisher/css/font-wso2.css">
<link rel="stylesheet" type="text/css" href="/publisher/themes/default/default-theme.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<!--
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/

@ -1,6 +1,7 @@
{
"Title" : "Title",
"Description" : "Description",
"ShortDescription" : "Short Description",
"Category" : "Category",
"Visibility" : "Visibility",
"Devices" : "Devices",
@ -8,22 +9,26 @@
"Groups" : "Groups",
"Tags" : "Tags",
"Platform" : "Platform",
"Platforms" : "Platfomrs",
"Platforms" : "Platforms",
"Applications": "Applications",
"No.Platform" : "No Platforms",
"Screenshots" : "Screenshots",
"Icon" : "Icon",
"Info" : "Info",
"Banner" : "Banner",
"Create.Application" : "Create Application",
"Back" : "Back",
"Cancel" : "Cancel",
"Finish" : "Finish",
"Continue" : "Continue",
"Name" : "Name",
"Application.Name" : "Application Name",
"General" : "General",
"App.Releases" : "Application Releases",
"Package.Manager" : "Package Manager",
"Save" : "Save",
"Create.Release" : "Create Release",
"Release.Channel" : "Release Channel",
"Release" : "Release",
"New.Release.For" : "New Release for",
"Upload.Package.File" : "Upload Package File",
@ -37,5 +42,20 @@
"Alpha.Releases" : "Alpha Releases",
"Version" : "Version",
"Status" : "Status",
"App.Publisher" : "Application Publisher"
"App.Publisher" : "Application Publisher",
"Search.Apps" : "Search for Applications",
"View.In.Store" : "View in Store",
"Last.Updated" : "Last updated on",
"Installs" : "Installs",
"General.Info" : "General Info",
"Select.Platform": "Select Platform",
"Add.Release" : "Add Release to Application",
"Share.With.Tenants" : "Share with Tenants",
"Disable" : "Disable",
"File.Based" : "File Based",
"Activate" : "Activate",
"Yes" : "Yes",
"No" : "No",
"No.Platform.Tags" : "No Platform Tags",
"Create.Platform" : "Create Platform"
}

@ -1,48 +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.
*/
.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;
}

@ -16,10 +16,131 @@
* under the License.
*/
@font-face {
font-family: "Roboto-Medium";
src: url('../../fonts/Roboto-Medium.woff');
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.ttf") format("ttf");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff") format("woff");
src: local("Roboto-Medium"), url("../../fonts/Roboto-Medium.woff2") format("woff2");
}
@font-face {
font-family: "Roboto-Regular";
src: url("../../fonts/Roboto-Regular.woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.ttf") format("ttf");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff") format("woff");
src: local("Roboto-Regular"), url("../../fonts/Roboto-Regular.woff2") format("woff2");
}
/*Colors*/
.primary {
color: white;
background-color: #2196f3 !important;
}
.primary-flat {
color: #2196F3 !important;
}
.danger {
color: white;
background-color: #e91e63 !important;
}
.danger-flat {
color: #e91e63 !important;
}
.grey {
color: #b3b3b3 !important;
}
/* ==================================================================== */
/* Custom button styles based on material design specs. */
.custom-raised {
font-family: Roboto-Medium;
text-transform: uppercase !important;
font-size: 14px !important;
padding-left: 16px !important;
border-radius: 2px !important;
padding-right: 16px !important;
height: 36px !important;
border: none !important;
}
.custom-raised:hover {
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
background-color: #1976D2 !important;
}
.custom-raised:focus {
box-shadow: none !important;
-webkit-box-shadow: none !important;
background-color: #1976D2 !important;
}
.custom-flat {
font-family: Roboto-Medium;
height: 36px !important;
border-radius: 2px !important;
margin-left: 8px !important;
margin-right: 8px !important;
padding-left: 8px !important;
padding-right: 8px !important;
background-color: transparent !important;
text-transform: uppercase;
outline: none !important;
border: none !important;
}
.custom-flat:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.12) !important;
}
.custom-flat:focus {
outline: none !important;
border: none !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
background-color: rgba(0, 0, 0, 0.40) !important;
}
.circle-button {
border-radius: 100% !important;
height: 36px !important;
width: 36px;
}
/* ==================================================================== */
/* Body Styling */
body {
width: 100%;
font-family: Roboto sans-serif;
font-family: "Roboto-Regular" !important;
background-color: #e8e8e8 !important;
}
.app-manager-title {
font-family: "Roboto-Medium";
font-size: 20px;
}
.app-manager-sub-title {
font-family: "Roboto-Regular";
font-size: 18px;
}
#app-mgt-footer {
clear: both;
position: relative;
height: 50px;
width: 100%;
color: white;
background-color: #334d88;
}
/* Login page styles*/
@ -31,35 +152,55 @@ body {
border-radius: 0;
}
#login-btn {
border-radius: 0;
background-color: navy;
color: white;
cursor: pointer;
.login-btn {
float: right;
}
#login-container {
width: 50%;
margin: 0 auto
.login-header {
background-color: #3f50b5;
color: white;
height: 128px;
width: 100%;
margin: 0 !important;
padding: 20px;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#login-card {
width: 25%;
height: 50%;
margin: 10% auto;
font-family: Roboto-Regular;
font-size: 14px;
border-radius: 0;
background-color: #BaBaBa;
background-color: #ffffff;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
/* Base layout container */
#container {
background-color: #ffffff;
padding: 0;
.login-header-title {
font-family: Roboto-Medium;
font-size: 20px;
font-weight: 500;
}
.login-header-logo {
height: 70px;
width: 150px;
}
.login-form {
margin: 0 !important;
padding: 40px;
}
/* Base layout container */
/* Base layout header content*/
#header-content {
height: 125px;
width: 100%;
.header-content {
height: 128px !important;
width: 100% !important;
margin: 0 10px 0 0;
background-color: #3b33bd;
background-color: #3f50b5 !important;
position: fixed; /* Set the navbar to fixed position */
top: 0; /* Position the navbar at the top of the page */
z-index: 2;
@ -67,22 +208,59 @@ body {
}
/* Contains the header styles.*/
#header {
margin: 16px 16px 20px 16px;
height: 100%;
.header {
padding: 24px 24px 10px 24px;
/*margin: 16px 16px 20px 16px;*/
position: relative;
}
#header-text {
color: #ffffff;
font-size: 25px;
font-size: 20px;
font-family: Roboto-Medium;
top: 10px;
margin-left: 10px;
}
/* The buttons in the header (User and Notification)*/
#header-btn-container {
float: right;
.header-button-container {
display: flex;
justify-content: flex-end;
}
.header-user-name {
font-family: Roboto-Medium;
font-size: 14px;
padding-top: 15px;
color: white;
}
.header-image {
height: 43px;
width: 100px;
margin-right: 24px;
}
#header-button {
border-radius: 50%;
background-color: transparent;
border: none;
height: 50px;
width: 50px;
margin-right: 10px;
position: relative;
outline: none;
}
#header-button:hover {
background-color: #4353bd;
cursor: pointer;
}
#header-button i {
position: absolute;
bottom: 19px;
left: 17px;
}
.btn-header {
@ -91,21 +269,25 @@ body {
color: white;
}
/* Search box styles */
.search-icon {
position: absolute;
top: 5px;
left: 5px;
#sub-title {
font-family: Roboto-Regular;
font-size: 18px;
font-weight: 600;
padding-top: 5px;
padding-left: 18px;
color: RGBA(0, 0, 0, 1);
}
#search-box {
/* Search box styles */
.search-box {
display: flex;
color: #a8a8a8;
position: relative;
float: right;
top: 75px;
left: 80px;
margin-right: 16px;
}
.search-box i {
position: absolute;
top: 5px;
color: #BaBaBa;
}
#search {
@ -123,38 +305,114 @@ body {
/* Application Add button */
#add-btn-container {
position: absolute;
left: 12%;
top: 100px;
top: 98px;
}
.add-btn {
background-color: #ff5722;
}
.add-btn:hover {
background-color: #E64A19;
}
#app-main-content {
margin: 0 auto;
}
#sub-title-container {
height: 100px;
padding: 50px 0 20px 0;
}
.application-container {
padding: 0 !important;
min-height: 100% !important;
margin-top: 128px !important;
}
/* 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;
background-color: white;
width: 100%;
box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 24px;
}
.stepper-header {
width: 100%;
border-bottom: solid 1px cornflowerblue;
color: #BaBaBa;
padding-bottom: 5px;
margin-bottom: 10px;
}
#application-tag {
margin: 0 2px 0 2px;
background-color: blue;
height: 20px;
}
#application-tag:hover {
cursor: pointer;
background-color: #007bff;
}
.platform-link-placeholder {
color: #888888;
float: right;
margin-right: 20px;
padding-bottom: 10px;
}
.platform-link-placeholder i {
margin-right: 4px;
}
#application-list {
margin-top: 20px;
transition: margin-right .5s;
}
#batch-content {
display: flex;
margin-top: 5px;
}
.app-list-icon {
border-radius: 50%;
height: 50px;
width: 50px
}
.app-table-row {
height: 62px;
cursor: pointer;
padding-top: 6px;
font-family: "Roboto-Regular";
font-size: medium;
}
.app-table-row:hover {
color: white;
background-color: #3f50b5;
}
.app-list-table-header {
margin-top: 30px;
margin-bottom: 10px;
font-family: "Roboto-Medium";
font-size: 15px;
}
.app-view-image {
height: 100px;
width: 100px;
border-radius: 50%;
}
#app-visibility-default {
display: none;
}
#app-image-screenshot {
width: 300px;
height: 300px;
@ -170,12 +428,15 @@ body {
height: 300px;
}
#form-error {
color: red;
}
.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;
}
@ -191,7 +452,6 @@ body {
margin: 0 5px 0 5px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
@ -206,7 +466,6 @@ body {
height: 150px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
@ -217,6 +476,7 @@ body {
}
#screenshot-container {
max-width: 600px;
display: flex;
overflow-x: auto;
height: 200px;
@ -232,6 +492,13 @@ body {
overflow-y: auto;
}
.step-index {
height: 20px;
width: 20px;
background-color: #2196F3;
border-radius: 50%;
}
#img-btn-screenshot {
margin: 0 5px 0 5px;
}
@ -239,6 +506,7 @@ body {
#app-create-modal {
max-width: 700px;
overflow-x: auto;
border-radius: 0% !important;
}
#store {
@ -276,12 +544,13 @@ body {
}
/* Application View */
#application-view-content {
width: 100%;
}
#application-view-row {
margin: 10px 10px 20px 20px;
margin: 10px 10px 0 20px;
}
#app-icon {
@ -293,7 +562,6 @@ body {
.app-updated-date {
color: #888888;
font-style: italic;
}
.app-install-count {
@ -315,12 +583,13 @@ body {
}
/* Application Edit Base Layout */
#application-edit-header {
height: 50px;
height: 40px;
width: 100%;
margin: 0;
font-size: 20px;
border-bottom: solid 1px #d8d8d8;
margin-top: 20px;
margin-bottom: 20px;
font-size: 25px;
}
.application-header-text {
@ -348,6 +617,7 @@ body {
}
/*Tab styling*/
div.tab {
float: left;
border-right: 1px solid #d8d8d8;
@ -355,6 +625,7 @@ div.tab {
}
/* Style the tab buttons */
div.tab button {
display: block;
background-color: inherit;
@ -369,30 +640,25 @@ div.tab button {
}
/* 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 {
@ -402,13 +668,13 @@ div.tab button.active {
.back-to-app {
position: absolute;
height: 40px;
width: 40px;
height: 50px;
width: 50px;
border-radius: 50%;
}
.back-to-app i {
padding: 10px 10px 10px 10px;
padding: 12px 10px 10px 12px;
}
.back-to-app:hover {
@ -418,6 +684,7 @@ div.tab button.active {
}
/* Create Release and Release management */
.release-header {
margin-top: 20px;
margin-bottom: 20px;
@ -472,6 +739,7 @@ div.tab button.active {
}
/* Application Edit General Info */
.app-edit-general-info {
margin-top: 20px;
max-width: 100%;
@ -481,3 +749,92 @@ div.tab button.active {
float: right;
margin-bottom: 10px;
}
.app-view-field {
font-family: Roboto-Medium;
font-size: 14px;
}
.app-view-text {
font-family: Roboto-Regular;
font-size: 14px;
}
/* Platform Specific Styles. */
#platform-listing {
margin: 10px;
}
.create-platform i {
margin-right: 10px;
}
#platform-list {
margin-top: 20px;
display: flex;
flex-flow: wrap;
}
.platform-content {
margin: 10px;
padding-top: 16px;
box-shadow: 2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.platform-content .row {
margin: 0;
}
.platform-content .col {
padding: 0;
}
.platform-content-basic {
padding: 0 16px 0 16px;
display: flex;
}
.platform-content-more-outer {
}
.platform-content-more {
padding: 16px 16px 24px 16px;
}
.platform-content-footer {
display: flex;
padding: 8px 8px 8px 8px;
}
.platform-text-container {
padding: 8px 16px 0 16px;
}
.circle-button {
float: right;
}
.platform-icon-letter {
text-align: center;
text-transform: uppercase;
font-family: Roboto-Medium;
font-size: 70px;
color: white;
padding-top: 15px;
}
.platform-icon-container {
height: 120px;
width: 120px;
background-color: #01579B;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
-webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !important;
}
.platform-content:hover {
}
.data-table-row-cell {
padding-top: 14px;
}

@ -35,8 +35,8 @@ export default class ApplicationMgtApi {
*
* From applicationData, the proper application object will be created and send it to the api.
* */
static createApplication(applicationData) {
let {application, images} = Helper.buildApplication(applicationData);
static createApplication(generalInfo, platform, screenshots, release) {
let {application, images} = Helper.buildApplication(generalInfo, platform, screenshots, release);
const headers = AuthHandler.createAuthenticationHeaders("application/json");
console.log(application);
console.log(images);

@ -25,26 +25,22 @@ export default class Helper {
/**
* Generate application object from form data passed.
* @param appData: Application data from the application creation form.
* @param generalInfo: Application data from the application creation form.
* @param platform
* @param screenshots
* @param release
* @return {Object, Object}: The application object and the set of images related to the application.
* */
static buildApplication(appData) {
static buildApplication(generalInfo, platform, screenshots, release) {
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];
}
let images = screenshots;
let application = Object.assign({}, generalInfo, platform);
for (let prop in application) {
if (prop === 'tags') {
application[prop] = Helper.stringifyTags(generalInfo[prop]);
}
}
console.log(application);
return {application, images};
}

@ -15,24 +15,24 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
'use strict';
/**
* Platform view component.
* This Utility class will contain basic methods for form validation.
* */
class PlatformView extends Component {
export const validateURL = (value) => {
};
constructor() {
super();
}
export const validateNull = (value) => {
return !value;
};
render() {
return (
<div>
Platform View
</div>
);
}
}
export const validateEmpty = (array) => {
return array.length > 0;
};
export default PlatformView;
export const validateEmptyObject = (object) => {
return Object.keys(object).length > 0;
};

@ -20,10 +20,11 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import AuthHandler from "../../api/authHandler";
import {Button, Col, Container, Input, Row,} from 'reactstrap';
import ApplicationCreate from '../Application/Create/ApplicationCreate';
import {Col, Container, Input, Row,} from 'reactstrap';
import FloatingButton from "../UIComponents/FloatingButton/FloatingButton";
import {FormattedMessage} from 'react-intl';
import Logo from "../UIComponents/Logo/Logo";
/**
* Base Layout:
@ -35,13 +36,21 @@ class BaseLayout extends Component {
constructor() {
super();
this.logout = this.logout.bind(this);
this.closeModal = this.closeModal.bind(this);
this.onClickPlatforms = this.onClickPlatforms.bind(this);
this.onClickApplications = this.onClickApplications.bind(this);
this.state = {
notifications: 0,
user: 'Admin',
openModal: false
user: '',
openModal: false,
currentPage: "",
logo: {}
};
this.logout = this.logout.bind(this);
this.closeModal = this.closeModal.bind(this);
}
componentWillMount() {
this.setState({user: this.props.user});
}
handleApplicationClick() {
@ -70,21 +79,56 @@ class BaseLayout extends Component {
this.setState({openModal: false});
}
onClickPlatforms() {
window.location.href = "/publisher/assets/platforms";
this.setState({currentPage: "Platforms"})
}
onClickApplications() {
window.location.href = "/publisher/assets/apps";
}
getCurrentPageTitle() {
let href = window.location.href;
if (href.indexOf("apps") !== -1) {
return "Applications";
} else if (href.indexOf("platforms") !== -1) {
return "Platforms";
}
}
render() {
const userName = this.state.user._userName[0];
return (
<Container noGutters fluid id="container">
<div id="header-content">
<div id="header">
<div>
<div className="header-content">
<div className="header">
<Row>
<Col md="6">
<span id="header-text">
WSO2 IoT <FormattedMessage id="App.Publisher" defaultMessage="Application Publisher"/>
<Logo className="header-image" image_name="logo.png"/>
IoT <FormattedMessage id="App.Publisher" defaultMessage="Application Publisher"/>
</span>
<div id="header-btn-container">
<i className="fw fw-notification btn-header"></i>
<i className="fw fw-user btn-header"></i>
</Col>
<Col>
<div className="header-button-container">
<Button id="header-button">
<i className="fw fw-notification btn-header"></i></Button>
<span className="header-user-name">
{userName.charAt(0).toUpperCase() + userName.slice(1)}
</span>
<Button id="header-button">
<i className="fw fw-user btn-header"></i></Button>
</div>
<div id="search-box">
<i className="fw fw-search search-icon">
</i>
</Col>
</Row>
<Row>
<Col>
<div className="search-box">
<i className="fw fw-search"></i>
<Input
id="search"
name="search"
@ -92,23 +136,55 @@ class BaseLayout extends Component {
onChange={(event) => console.log(event.target.value)} //TODO: Remove this
/>
</div>
</Col>
</Row>
</div>
<Container>
<div id="add-btn-container">
<FloatingButton
className="add-btn small"
onClick={this.handleApplicationCreateClick.bind(this)}
/>
</div>
</Container>
</div>
<Container className="application-container">
<div id="app-main-content">
<Row id="sub-title-container">
<Col>
<div id="sub-title">
{/*TODO: Add the current page title*/}
{this.getCurrentPageTitle()}
</div>
</Col>
<Col>
<div className="platform-link-placeholder">
{this.getCurrentPageTitle() === "Applications" ?
<Button className="custom-flat grey" onClick={this.onClickPlatforms}>
<i className="fw fw-settings"></i>
<FormattedMessage id="Platforms" defaultMessage="Platforms"/>
</Button> :
<Button className="custom-flat grey" onClick={this.onClickApplications}>
<i className="fw fw-application"></i>
<FormattedMessage id="Applications" defaultMessage="Applications"/>
</Button>
}
</div>
<div id="application-content" style={this.state.style}>
</Col>
</Row>
<Row>
<div id="application-content">
<Row>
<Col>
{this.props.children}
</Col>
</Row>
</div>
<ApplicationCreate open={this.state.openModal} close={this.closeModal}/>
</Row>
</div>
</Container>
<ApplicationCreate open={this.state.openModal} close={this.closeModal}/>
</div>
);
}
}

@ -18,10 +18,11 @@
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {Button, Col, Row, Table} from 'reactstrap';
import {Button, Col, Row} from 'reactstrap';
import Drawer from '../UIComponents/Drawer/Drawer';
import ApplicationView from './View/ApplicationView';
import {FormattedMessage} from 'react-intl';
import ApplicationMgtApi from "../../api/applicationMgtApi";
import AuthHandler from "../../api/authHandler";
/**
* The App Create Component.
@ -32,14 +33,15 @@ import {FormattedMessage} from 'react-intl';
* 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.onAppEditClick = this.onAppEditClick.bind(this);
this.getSelectedApplication = this.getSelectedApplication.bind(this);
this.state = {
searchedApplications: [],
applications: [],
@ -57,7 +59,6 @@ class ApplicationListing extends Component {
};
}
applications = [
{
id: "3242342ffww3423",
@ -84,35 +85,59 @@ class ApplicationListing extends Component {
},
];
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);
// });
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "applicationName",
data_type: "string",
sortable: true,
locale: "Application.name",
label: "Application Name",
sort: this.sortData
},
{
data_id: "platform",
data_type: "image_array",
sortable: false,
locale: "Platform",
label: "Platform"
},
{
data_id: "category",
data_type: "string",
sortable: false,
locale: "Category",
label: "Category"
},
{
data_id: "status",
data_type: "string",
sortable: false,
locale: "Status",
label: "Status"
},
{
data_id: "edit",
data_type: "button",
sortable: false,
label: ""
}
];
/**
* 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);
}
componentWillMount() {
this.setState({searchedApplications: apps});
let getApps = ApplicationMgtApi.getApplications();
getApps.then(response => {
console.log(response);
this.setState({searchedApplications: response.data.applications});
}).catch(err => {
AuthHandler.unauthorizedErrorHandler(err);
});
}
/**
@ -154,34 +179,22 @@ class ApplicationListing extends Component {
return 0;
}
onRowClick() {
onRowClick(uuid) {
let selectedApp = this.getSelectedApplication(uuid);
let style = {
width: '500px',
marginLeft: '500px'
width: '550px',
marginLeft: '550px'
};
let appListStyle = {
marginRight: '500px',
marginRight: '550px',
};
this.setState({drawer: style, appListStyle: appListStyle});
}
handleButtonClick() {
console.log("Application Listing");
this.props.history.push("apps/edit/fdsfdsf343");
this.setState({drawer: style, appListStyle: appListStyle, application: selectedApp[0]});
}
remove(imageId) {
let tmp = this.state.image;
console.log(imageId);
let rem = tmp.filter((image) => {
return image.id !== imageId
});
this.setState({image: rem});
onAppEditClick(uuid) {
this.props.history.push("apps/edit/" + uuid);
}
closeDrawer() {
@ -196,70 +209,56 @@ class ApplicationListing extends Component {
this.setState({drawer: style, appListStyle: appListStyle});
}
getSelectedApplication(uuid) {
return this.state.searchedApplications.filter(application => {
return application.uuid === uuid;
});
}
render() {
//TODO: Move this to a data table component.
return (
<div id="application-list" style={this.state.appListStyle}>
<Row>
<Col xs="3 offset-9">
<div className="platform-link-placeholder">
<Button><i className="fw fw-settings"></i>
<FormattedMessage id="Platforms" defaultMessage="Platforms"/>
</Button>
</div>
</Col>
<Row className="app-list-table-header">
{this.headers.map(header => {
if (header.data_id === "applicationName") {
return (
<Col xs="5">{header.label}</Col>)
} else if (header.data_id === "image") {
return (<Col xs="1">{header.label}</Col>)
}
return (<Col>{header.label}</Col>)
})}
</Row>
<Row>
<Col>
<Table striped hover>
<thead>
<tr>
<th></th>
{/* TODO: Remove console.log and add sort method. */}
<th onClick={() => {
console.log("sort")
}}>
<FormattedMessage id="Application.Name" defaultMessage="Application Name"/>
</th>
<th><FormattedMessage id="Category" defaultMessage="Category"/></th>
<th><FormattedMessage id="Platform" defaultMessage="Platform"/></th>
<th><FormattedMessage id="Status" defaultMessage="Status"/></th>
<th></th>
</tr>
</thead>
<tbody>
{this.applications.map(
(application) => {
<hr/>
{this.state.searchedApplications.map(application => {
return (
<tr key={application.id} onClick={this.onRowClick}>
<td>
{/* TODO: Move this styles to css. */}
<Row className="app-table-row" onClick={() => {
this.onRowClick(application.uuid)
}}>
<Col xs="1">
<img
className="app-list-icon"
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}>
</Col>
<Col xs="5" className="data-table-row-cell"><strong>{application.name}</strong></Col>
<Col className="data-table-row-cell">{application.platform.name}</Col>
<Col className="data-table-row-cell">{application.category.name}</Col>
<Col
className="data-table-row-cell">{application.currentLifecycle.lifecycleState.name}
</Col>
<Col>
<Button className="custom-flat grey rounded"
onClick={() => this.onAppEditClick(application.uuid)}>
<i className="fw fw-edit"></i>
</Button>
</td>
</tr>
)
}
)}
</tbody>
</Table>
</Col>
</Row>
)
})}
<Drawer onClose={this.closeDrawer.bind(this)} style={this.state.drawer}>
<ApplicationView/>
<ApplicationView application={this.state.application}/>
</Drawer>
</div>
);

@ -21,9 +21,10 @@ import {withRouter} from 'react-router-dom';
import AuthHandler from "../../../api/authHandler";
import {Step1, Step2, Step3, Step4} from './CreateSteps/index';
import ApplicationMgtApi from '../../../api/applicationMgtApi';
import {Button, Col, Modal, ModalBody, ModalFooter, ModalHeader, Row} from 'reactstrap';
import {Col, Modal, ModalBody, ModalHeader, Row} from 'reactstrap';
import {FormattedMessage} from 'react-intl';
/**
* The App Create Component.
*
@ -49,7 +50,11 @@ class ApplicationCreate extends Component {
finished: false,
stepIndex: 0,
stepData: [],
isDialogOpen: false
isDialogOpen: false,
generalInfo: {},
platform: {},
screenshots: {},
release: {}
};
}
@ -61,17 +66,18 @@ class ApplicationCreate extends Component {
this.setState({open: this.props.open});
}
/**
* Resets the form and closes the modal.
* */
onClose() {
this.setState({stepIndex: 0}, this.props.close());
this.setState({stepIndex: 0, generalInfo: {}, platform: {}, screenshots: {}, release: {}}, this.props.close());
}
/**
* Handles next button click event.
* */
onNextClick() {
console.log("Handle Next"); //TODO: Remove this
console.log(this.state.stepIndex); //TODO: Remove this
const {stepIndex} = this.state;
this.setState({
stepIndex: stepIndex + 1,
@ -83,8 +89,8 @@ class ApplicationCreate extends Component {
* Handles form submit.
* */
onSubmit() {
let stepData = this.state.stepData;
let applicationCreationPromise = ApplicationMgtApi.createApplication(stepData);
let {generalInfo, platform, screenshots, release} = this.state;
let applicationCreationPromise = ApplicationMgtApi.createApplication(generalInfo, platform, screenshots, release);
applicationCreationPromise.then(response => {
this.handleYes();
}
@ -108,10 +114,10 @@ class ApplicationCreate extends Component {
* This clears the data in the current step and returns to the previous step.
* */
onPrevClick() {
console.log(this.state.stepIndex);
const {stepIndex} = this.state;
if (stepIndex > 0) {
this.removeStepData();
this.setState({stepIndex: stepIndex - 1});
this.setState({stepIndex: stepIndex - 1, finished: false});
}
};
@ -121,11 +127,25 @@ class ApplicationCreate extends Component {
* @param data: The form data of the step.
* */
setStepData(step, data) {
console.log(step, data, this.state.stepData); //TODO: Remove this
let tmpStepData = this.state.stepData;
tmpStepData.push({step: step, data: data});
this.setState({stepData: tmpStepData}, this.onNextClick())
console.log(step, data, this.state); //TODO: Remove this
switch (step) {
case "generalInfo": {
this.setState({generalInfo: data}, this.onNextClick());
break;
}
case "platform": {
this.setState({platform: data}, this.onNextClick());
break;
}
case "screenshots": {
this.setState({screenshots: data}, this.onNextClick());
break;
}
case "release": {
this.setState({release: data}, this.onNextClick());
break;
}
}
};
/**
@ -134,9 +154,10 @@ class ApplicationCreate extends Component {
removeStepData() {
let tempData = this.state.stepData;
tempData.pop();
this.setState({stepData: tempData});
this.setState({stepData: tempData, stepIndex: 0});
};
/* ----------------- Deprecated ----------------- */
/**
* Handles the Yes button in app creation cancellation dialog.
* Clears all the form data and reset the wizard.
@ -153,6 +174,8 @@ class ApplicationCreate extends Component {
this.setState({isDialogOpen: false});
};
/* ---------------------------------------------- */
/**
* Defines all the Steps in the stepper. (Wizard)
*
@ -167,35 +190,36 @@ class ApplicationCreate extends Component {
case 0:
return (
<Step1
handleNext={this.onNextClick}
setData={this.setStepData}
removeData={this.removeStepData}
defaultData={this.state.generalInfo}
setStepData={this.setStepData}
close={this.onClose}
/>
);
case 1:
return (
<Step2
handleNext={this.onNextClick}
defaultData={this.state.platform}
handlePrev={this.onPrevClick}
setData={this.setStepData}
removeData={this.removeStepData}
setStepData={this.setStepData}
close={this.onClose}
/>
);
case 2:
return (
<Step3
handleFinish={this.onNextClick}
defaultData={this.state.screenshots}
handlePrev={this.onPrevClick}
setData={this.setStepData}
removeData={this.removeStepData}
setStepData={this.setStepData}
close={this.onClose}
/>
);
case 3: {
return (
<Step4
handleNext={this.onNextClick}
setData={this.setStepData}
removeData={this.removeStepData}
defaultData={this.state.release}
handlePrev={this.onPrevClick}
onSubmit={this.onSubmit}
close={this.onClose}
/>
)
}
@ -204,6 +228,10 @@ class ApplicationCreate extends Component {
}
}
setStepHeader(stepIndex) {
}
render() {
const {finished, stepIndex} = this.state;
@ -218,7 +246,35 @@ class ApplicationCreate extends Component {
<Row>
<Col>
<div className="stepper-header">
<Row>
<Col>
<div className="stepper-header-content">
<div className="step-index">1</div>
<div className="step-header">
<FormattedMessage id="General.Info" defaultMessage="General.Info"/>
</div>
</div>
</Col>
<Col>
<span className="step-index">2</span>
<span className="step-header">
<FormattedMessage id="Select.Platform"
defaultMessage="Select.Platform"/>
</span>
</Col>
<Col>
<span className="step-index">3</span>
<span className="step-header">
<FormattedMessage id="Screenshots" defaultMessage="Screenshots"/>
</span>
</Col>
<Col>
<span className="step-index">4</span>
<span className="step-header">
<FormattedMessage id="Release" defaultMessage="Release"/>
</span>
</Col>
</Row>
</div>
</Col>
</Row>
@ -228,22 +284,6 @@ class ApplicationCreate extends Component {
</Col>
</Row>
</ModalBody>
<ModalFooter>
{stepIndex === 0 ? <div/> :
<Button color="primary" onClick={this.onPrevClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>}
<Button color="secondary" onClick={this.onClose}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
{finished ?
<Button color="primary" onClick={this.onSubmit}>
<FormattedMessage id="Finish" defaultMessage="Finish" />
</Button> :
<Button color="primary" onClick={this.onNextClick}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>}
</ModalFooter>
</Modal>
</div>);
}

@ -18,8 +18,10 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Badge, FormGroup, Input, Label} from 'reactstrap';
import {FormattedMessage} from 'react-intl';
import {Button, Form, FormFeedback, FormGroup, Input, Label, ModalFooter} from 'reactstrap';
import * as validator from '../../../../common/validator';
import Chip from "../../../UIComponents/Chip/Chip";
/**
* The Second step of application create wizard.
@ -42,22 +44,34 @@ import {FormattedMessage} from 'react-intl';
class Step1 extends Component {
constructor() {
super();
this.onTextFieldChange = this.onTextFieldChange.bind(this);
this.setStepData = this.setStepData.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onVisibilityChange = this.onVisibilityChange.bind(this);
this.onVisibilityItemSelect = this.onVisibilityItemSelect.bind(this);
this.handleRequestDelete = this.handleRequestDelete.bind(this);
this.validate = this.validate.bind(this);
this.state = {
tags: [],
icon: [],
title: "",
name: "",
errors: {},
banner: [],
defValue: "",
category: 0,
visibility: 0,
category: {},
visibility: {type: "PUBLIC", allowedList: []},
description: "",
screenshots: [],
identifier: "",
shortDescription: ""
};
}
componentWillMount() {
const defaultVals = this.props.defaultData;
if (defaultVals) {
this.setState(defaultVals);
}
}
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
@ -85,82 +99,223 @@ class Step1 extends Component {
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(event) {
this.chipData = this.state.tags;
console.log(event.target);
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
handleRequestDelete(key) {
let chipData = this.state.tags;
const chipToDelete = chipData.map((chip) => chip.key).indexOf(key);
chipData.splice(chipToDelete, 1);
this.setState({tags: chipData});
};
/**
* Creates an object with the current step data and persist in the parent.
* */
setStepData() {
let stepData = {};
this.props.setData("step1", {step: stepData});
const {name, description, tags, visibility, shortDescription} = this.state;
let stepData = {
name: name,
description: description,
tags: tags,
visibility: visibility,
shortDescription: shortDescription,
category: {id: 1, name: "business"}
};
let {errorCount, errors} = this.validate();
if (errorCount !== 0) {
this.setState({errors: errors});
} else {
this.props.setStepData("generalInfo", stepData);
}
};
onCancelClick() {
this.props.close();
}
/**
* Validate the form fields.
* */
validate() {
const {name, description, tags, shortDescription} = this.state;
let errorCount = 0;
let errors = {};
if (validator.validateNull(name)) {
errorCount++;
errors.name = "Application Title is Required!";
}
if (validator.validateNull(description)) {
errorCount++;
errors.description = "Description is Required!"
}
if (validator.validateNull(shortDescription)) {
errorCount++;
errors.shortDescription = "Short Description is Required!"
}
if (!validator.validateEmpty(tags)) {
errorCount++;
errors.tags = "You need to enter at least one tag!"
}
return {errorCount, errors};
}
/**
* Set text field values to state.
* */
onTextFieldChange(event, value) {
let field = event.target.id;
onTextFieldChange(event) {
let field = event.target.name;
switch (field) {
case "name": {
this.setState({name: value});
case "appName": {
this.setState({name: event.target.value});
break;
}
case "shortDescription": {
this.setState({shortDescription: value});
case "appDescription": {
this.setState({description: event.target.value});
break;
}
case "description": {
this.setState({description: value});
break;
}
case "identifier": {
this.setState({identifier: value});
break;
case "appShortDescription": {
this.setState({shortDescription: event.target.value});
}
}
};
onVisibilityChange(event) {
console.log(event.target.value);
this.setState({visibility: event.target.value});
}
onVisibilityItemSelect(event) {
}
render() {
const {visibility} = this.state;
let visibilityItem = () => {
switch (visibility) {
case("public"): {
return <div/>
}
case("roles"): {
return <FormGroup>
<Input
type="select"
name="visibility-item"
id="app-visibility-item"
onChange={this.onVisibilityItemSelect}
>
<option id="app-visibility-default" disabled selected>Select the Roles.</option>
<option><Input type="checkbox"/>Role1</option>
<option>Role2</option>
</Input>
</FormGroup>
}
case ("groups"): {
return <FormGroup>
<Input
type="select"
name="visibility-item"
id="app-visibility-item"
onChange={this.onVisibilityItemSelect}
>
<option id="app-visibility-default" disabled selected>Select the Groups.</option>
<option>Group1</option>
<option>Group2</option>
</Input>
</FormGroup>
}
default: {
return <div/>
}
}
};
return (
<div className="createStep2Content">
<div>
<div>
<div>
<FormGroup>
<Label for="app-title">
<FormattedMessage id='Title' defaultMessage='Title'/>*
</Label>
<Input required type="text" name="appName" id="app-title"/>
<Input
required
type="text"
name="appName"
id="app-title"
value={this.state.name}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.name}</FormFeedback>
</FormGroup>
<FormGroup>
<Label for="app-short-description">
<FormattedMessage id="shortDescription" defaultMessage="shortDescription"/>*
</Label>
<Input
required
type="textarea"
name="appShortDescription"
id="app-short-description"
value={this.state.shortDescription}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.shortDescription}</FormFeedback>
</FormGroup>
<FormGroup>
<Label for="app-description">
<FormattedMessage id='Description' defaultMessage='Description'/>*
</Label>
<Input required type="textarea" name="appDescription" id="app-description"/>
<Input
required
type="textarea"
name="appDescription"
id="app-description"
value={this.state.description}
onChange={this.onTextFieldChange}
/>
<FormFeedback id="form-error">{this.state.errors.description}</FormFeedback>
</FormGroup>
<FormGroup>
<Label for="app-category">
<FormattedMessage id='Category' defaultMessage='Category'/>
</Label>
<Input type="select" name="category" id="app-category">
<option>Business</option>
<Input
type="select"
name="category"
id="app-category"
>
<option key={0} value={{id: 0, name: "business"}}>Business</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="app-visibility">
<FormattedMessage id='Visibility' defaultMessage='Visibility'/>
</Label>
<Input type="select" name="visibility" id="app-visibility">
<option><FormattedMessage id='Devices' defaultMessage='Devices'/></option>
<option><FormattedMessage id='Roles' defaultMessage='Roles'/></option>
<option><FormattedMessage id='Groups' defaultMessage='Groups'/></option>
<Form inline>
<FormGroup>
<Input
type="select"
name="visibility"
id="app-visibility"
onChange={this.onVisibilityChange}
>
<option id="app-visibility-default" disabled selected>Select the App Visibility
Option.
</option>
<option key={1}><FormattedMessage id='Devices' defaultMessage='Devices'/>
</option>
<option key={2}><FormattedMessage id='Roles' defaultMessage='Roles'/></option>
<option key={3}><FormattedMessage id='Groups' defaultMessage='Groups'/></option>
</Input>
</FormGroup>
{visibilityItem()}
</Form>
</FormGroup>
<FormGroup>
<Label for="app-tags"><FormattedMessage id='Tags' defaultMessage='Tags'/>*</Label>
<Input
@ -172,23 +327,31 @@ class Step1 extends Component {
onChange={this.handleTagChange.bind(this)}
onKeyPress={this.addTags.bind(this)}
/>
<div id="batch-content">
{this.state.tags.map(tag => {
return (
<Badge
style={{margin: '0 2px 0 2px'}}
value={tag.value}
onClick={this.handleRequestDelete.bind(this)}
>
{tag.value}
</Badge>
<Chip
key={tag.key}
content={tag}
onDelete={this.handleRequestDelete}
/>
)
}
)}
</div>
<FormFeedback id="form-error">{this.state.errors.tags}</FormFeedback>
</FormGroup>
</div>
</div>
<ModalFooter>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div>
);
}
@ -197,8 +360,7 @@ class Step1 extends Component {
Step1.prototypes = {
handleNext: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
setData: PropTypes.func
};
export default Step1;

@ -20,8 +20,9 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react';
import AuthHandler from "../../../../api/authHandler";
import PlatformMgtApi from "../../../../api/platformMgtApi";
import {FormGroup, Input, Label} from 'reactstrap';
import {Button, FormFeedback, FormGroup, Input, Label, ModalFooter} from 'reactstrap';
import {FormattedMessage} from 'react-intl';
import * as validator from '../../../../common/validator';
/**
* The first step of the application creation wizard.
@ -41,24 +42,30 @@ class Step2 extends Component {
super();
this.setPlatforms = this.setPlatforms.bind(this);
this.setStepData = this.setStepData.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.validate = this.validate.bind(this);
this.platforms = [];
this.state = {
finished: false,
stepIndex: 0,
errors: {},
store: 1,
platformSelectedIndex: 0,
platform: "",
platforms: [],
stepData: [],
title: "",
titleError: ""
platform: {},
platforms: []
};
}
componentWillMount() {
const {defaultData} = this.props;
if (defaultData) {
this.setState(defaultData);
}
}
componentDidMount() {
//Get the list of available platforms and set to the state.
PlatformMgtApi.getPlatforms().then(response => {
console.log(response);
this.setPlatforms(response.data);
}).catch(err => {
AuthHandler.unauthorizedErrorHandler(err);
@ -76,25 +83,51 @@ class Step2 extends Component {
platform = platforms[index];
tmpPlatforms.push(platform);
}
this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0, platform: tmpPlatforms[0].name})
this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0})
}
/**
* Persist the current form data to the state.
* */
setStepData() {
let step = {
store: this.state.store,
platform: this.state.platforms[this.state.platformSelectedIndex]
const {store, platform} = this.state;
let data = {
store: store,
platform: platform[0]
};
this.props.setData("step2", {step: step});
const {errorCount, errors} = this.validate();
if (errorCount > 0) {
this.setState({errors: errors})
} else {
this.props.setStepData("platform", data);
}
}
onCancelClick() {
this.props.close();
}
onBackClick() {
this.props.handlePrev();
}
validate() {
const {store, platform} = this.state;
let errors = {};
let errorCount = 0;
if (!validator.validateEmptyObject(platform)) {
errorCount++;
errors.platform = "You must select an application platform!"
}
return {errorCount, errors};
}
/**
* Triggers when changing the Platform selection.
* */
onChangePlatform(event) {
console.log(event.target.value, this.state.platforms);
let id = event.target.value;
let selectedPlatform = this.state.platforms.filter((platform) => {
return platform.identifier === id;
@ -122,16 +155,34 @@ class Step2 extends Component {
</FormGroup>
<FormGroup>
<Label for="store"><FormattedMessage id='Platform' defaultMessage='Platform'/></Label>
<Input type="select" name="store" onChange={this.onChangePlatform.bind(this)}>
<Input
required
type="select"
name="store"
onChange={this.onChangePlatform.bind(this)}
>
<option id="app-visibility-default" disabled selected>Select the Application Platform</option>
{this.state.platforms.length > 0 ? this.state.platforms.map(platform => {
return (
<option value={platform.identifier}>
<option value={platform.identifier} key={platform.identifier}>
{platform.name}
</option>
)
}) : <option><FormattedMessage id='No.Platform' defaultMessage='No Platforms'/></option>}
</Input>
<FormFeedback id="form-error">{this.state.errors.platform}</FormFeedback>
</FormGroup>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div>
);
}

@ -19,7 +19,8 @@
import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import {FormGroup, Label} from 'reactstrap';
import * as validator from '../../../../common/validator';
import {Button, FormFeedback, FormGroup, Label, ModalFooter} from 'reactstrap';
import AppImage from "../../../UIComponents/AppImage/AppImage";
import {FormattedMessage} from 'react-intl';
@ -40,47 +41,77 @@ import {FormattedMessage} from 'react-intl';
class Step3 extends Component {
constructor() {
super();
this.setStepData = this.setStepData.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.validate = this.validate.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.state = {
tags: [],
icon: [],
title: "",
errors: {},
banner: [],
defValue: "",
category: 0,
visibility: 0,
description: "",
screenshots: [],
identifier: "",
shortDescription: ""
};
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(event) {
this.chipData = this.state.tags;
console.log(event.target); //TODO: Remove Console log.
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
};
componentWillMount() {
const {defaultData} = this.props;
this.setState(defaultData);
}
/**
* Creates an object with the current step data and persist in the parent.
* */
setStepData() {
const {icon, banner, screenshots} = this.state;
let stepData = {
icon: this.state.icon,
banner: this.state.banner,
screenshots: this.state.screenshots
icon: icon,
banner: banner,
screenshots: screenshots
};
this.props.setData("step2", {step: stepData});
const {errorCount, errors} = this.validate();
if (errorCount > 0) {
this.setState({errors: errors})
} else {
this.props.setStepData("screenshots", stepData);
}
};
onCancelClick() {
this.props.close();
}
onBackClick() {
this.props.handlePrev();
}
validate() {
const {icon, banner, screenshots} = this.state;
let errors = {}, errorCount = 0;
if (!validator.validateEmpty(icon)) {
errorCount++;
errors.icon = "You must upload an icon image!"
}
if (!validator.validateEmpty(banner)) {
errorCount++;
errors.banner = "You must upload a banner image!"
}
if (!validator.validateEmpty(screenshots)) {
errorCount++;
errors.screenshots = "You must upload at least one screenshot image!"
}
return {errorCount, errors};
}
/**
* Removed user uploaded banner.
* */
@ -126,7 +157,6 @@ class Step3 extends Component {
onDrop={(screenshots, rejected) => {
let tmpScreenshots = this.state.screenshots;
tmpScreenshots.push(screenshots);
console.log(screenshots); //TODO: Remove this
this.setState({
screenshots: tmpScreenshots
});
@ -135,6 +165,7 @@ class Step3 extends Component {
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
<FormFeedback id="form-error">{this.state.errors.screenshots}</FormFeedback>
</FormGroup>
</div>
<div style={{display: 'flex'}}>
@ -162,6 +193,7 @@ class Step3 extends Component {
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
<FormFeedback id="form-error">{this.state.errors.icon}</FormFeedback>
</FormGroup>
</div>
<div style={{marginLeft: '15px'}}>
@ -188,9 +220,21 @@ class Step3 extends Component {
</Dropzone> : <div/>
}
</div>
<FormFeedback id="form-error">{this.state.errors.banner}</FormFeedback>
</FormGroup>
</div>
</div>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.setStepData}>
<FormattedMessage id="Continue" defaultMessage="Continue"/>
</Button>
</ModalFooter>
</div>
);
}

@ -18,8 +18,9 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Collapse, FormGroup, Input, Label, FormText} from 'reactstrap';
import {Button, Collapse, FormGroup, FormText, Input, Label, ModalFooter} from 'reactstrap';
import Switch from '../../../UIComponents/Switch/Switch'
import {FormattedMessage} from 'react-intl';
/**
* The Third step of application create wizard. {Application Release Step}
@ -47,9 +48,10 @@ class Step4 extends Component {
constructor() {
super();
this.handleToggle = this.handleToggle.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.handleFinish = this.handleFinish.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
showForm: false,
releaseChannel: 1,
@ -66,13 +68,18 @@ class Step4 extends Component {
this.props.handleFinish();
}
/**
* Invokes Prev button click.
* */
handlePrev() {
onCancelClick() {
this.props.close();
}
onBackClick() {
this.props.handlePrev();
}
onSubmit() {
this.props.onSubmit();
}
/**
* Handles release application selection.
* */
@ -90,7 +97,7 @@ class Step4 extends Component {
<div id="app-release-switch-label">
<Label for="app-release-switch">
<strong>
Add Release to Application
<FormattedMessage id="Add.Release" defaultMessage="Add.Release"/>
</strong>
</Label>
</div>
@ -105,7 +112,7 @@ class Step4 extends Component {
<br/>
<div>
<FormText color="muted">
<i>Info: </i>
<i><FormattedMessage id="Info" defaultMessage="Info"/> </i>
Enabling this will create a release for the current Application.
To upload the Application, please visit to the Release management section of
Application Edit View.
@ -114,7 +121,9 @@ class Step4 extends Component {
{/*If toggle is true, the release form will be shown.*/}
<Collapse isOpen={this.state.showForm}>
<FormGroup>
<Label for="release-channel">Release Channel</Label>
<Label for="release-channel">
<FormattedMessage id="Release.Channel" defaultMessage="Release.Channel"/>
</Label>
<Input
type="select"
id="release-channel"
@ -130,7 +139,9 @@ class Step4 extends Component {
</Input>
</FormGroup>
<FormGroup>
<Label for="version">Version*</Label>
<Label for="version">
<FormattedMessage id="Version" defaultMessage="Version"/>*
</Label>
<Input
type="text"
id="version input-custom"
@ -140,6 +151,17 @@ class Step4 extends Component {
</FormGroup>
</Collapse>
</div>
<ModalFooter>
<Button className="custom-flat primary-flat" onClick={this.onBackClick}>
<FormattedMessage id="Back" defaultMessage="Back"/>
</Button>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary" onClick={this.onSubmit}>
<FormattedMessage id="Finish" defaultMessage="Finish"/>
</Button>
</ModalFooter>
</div>
);
}
@ -149,7 +171,7 @@ Step4.propTypes = {
handleFinish: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
onSubmit: PropTypes.func
};
export default Step4;

@ -89,6 +89,7 @@ class ApplicationEdit extends Component {
<FormattedMessage id="Application.Name" defaultMessage="Application Name"/>
</Col>
</Row>
<hr/>
<Row id="application-edit-main-container">
<Col xs="3">
<div className="tab">
@ -104,18 +105,10 @@ class ApplicationEdit extends Component {
</div>
</Col>
<Col xs="9">
<div id="app-edit-content">
<Row>
<Col xs="12">
<div id="application-edit-outer-content">
{/* Application edit content */}
<div id="application-edit-content">
{this.getTabContent(this.state.activeTab)}
</div>
</div>
</Col>
</Row>
</div>
</Col>
</Row>
</div>

@ -17,16 +17,24 @@
*/
import React, {Component} from 'react';
import {Badge, Button, FormGroup, Input, Label, Row} from 'reactstrap';
import {Button, FormGroup, Input, Label, Row} from 'reactstrap';
import Dropzone from 'react-dropzone';
import {FormattedMessage} from 'react-intl';
import Chip from "../../../UIComponents/Chip/Chip";
class GeneralInfo extends Component {
constructor() {
super();
this.onTextFieldChange = this.onTextFieldChange.bind(this);
this.addTags = this.addTags.bind(this);
this.handleRequestDelete = this.handleRequestDelete.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.state = {
defValue: "",
title: "",
description: "",
shortDescription: "",
tags: [],
screenshots: [],
icon: [],
@ -34,6 +42,62 @@ class GeneralInfo extends Component {
}
}
/**
* Set text field values to state.
* */
onTextFieldChange(event) {
let field = event.target.name;
console.log(event.target.value);
switch (field) {
case "appName": {
this.setState({name: event.target.value});
break;
}
case "appDescription": {
this.setState({description: event.target.value});
break;
}
case "appShortDescription": {
this.setState({shortDescription: event.target.value});
}
}
};
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""}, console.log(tags));
}
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(key) {
let chipData = this.state.tags;
const chipToDelete = chipData.map((chip) => chip.key).indexOf(key);
chipData.splice(chipToDelete, 1);
this.setState({tags: chipData});
};
//TODO: Remove Console logs.
render() {
return (
@ -44,18 +108,31 @@ class GeneralInfo extends Component {
<Label for="app-title">
<FormattedMessage id="Title" defaultMessage="Title"/>*
</Label>
<Input required type="text" name="appName" id="app-title"
onChange={this.onTextFieldChange}/>
</FormGroup>
<FormGroup>
<Label for="app-short-description">
<FormattedMessage id="shortDescription" defaultMessage="shortDescription"/>*
</Label>
<Input
required
type="text"
name="appName"
id="app-title"
type="textarea"
name="appShortDescription"
id="app-short-description"
onChange={this.onTextFieldChange}
/>
</FormGroup>
<FormGroup>
<Label for="app-title">
<FormattedMessage id="Description" defaultMessage="Description"/>*
</Label>
<Input required type="textarea" multiline name="appName" id="app-title"/>
<Input
required
type="textarea"
name="appDescription"
id="app-description"
onChange={this.onTextFieldChange}/>
</FormGroup>
<FormGroup>
<Label for="app-category">
@ -79,16 +156,23 @@ class GeneralInfo extends Component {
<Label for="app-tags">
<FormattedMessage id="Tags" defaultMessage="Tags"/>*
</Label>
<Input required type="text" value={this.state.defValue} name="app-tags" id="app-tags"/>
<Input
required
type="text"
value={this.state.defValue}
name="app-tags"
id="app-tags"
onChange={this.handleTagChange.bind(this)}
onKeyPress={this.addTags.bind(this)}
/>
<div id="batch-content">
{this.state.tags.map(tag => {
return (
<Badge
style={{margin: '0 2px 0 2px'}}
value={tag.value}
>
{tag.value}
</Badge>
<Chip
key={tag.key}
content={tag}
onDelete={this.handleRequestDelete}
/>
)
}
)}
@ -102,12 +186,14 @@ class GeneralInfo extends Component {
<span className="image-sub-title"> (600 X 800 32 bit PNG)</span>
<div id="screenshot-container">
{this.state.screenshots.map((tile) => (
<button id="img-btn-screenshot" style={{height: '210px', width: '410px'}}
<button id="img-btn-screenshot"
style={{height: '210px', width: '410px'}}
onMouseEnter={() => {
console.log("Mouse Entered")
}}>
{console.log(tile[0].preview)}
<img style={{height: '200px', width: '400px'}} src={tile[0].preview}/>
<img style={{height: '200px', width: '400px'}}
src={tile[0].preview}/>
</button>
))}
{this.state.screenshots.length < 3 ?
@ -140,7 +226,8 @@ class GeneralInfo extends Component {
<button onMouseEnter={() => {
console.log("Mouse Entered")
}}>
<img style={{height: '200px', width: '200px'}} src={tile.preview}/>
<img style={{height: '200px', width: '200px'}}
src={tile.preview}/>
</button>
))}
{this.state.icon.length === 0 ?
@ -167,7 +254,8 @@ class GeneralInfo extends Component {
<button onMouseEnter={() => {
console.log("Mouse Entered")
}}>
<img style={{height: '200px', width: '400px'}} src={tile.preview}/>
<img style={{height: '200px', width: '400px'}}
src={tile.preview}/>
</button>
))}
{this.state.banner.length === 0 ?
@ -186,9 +274,8 @@ class GeneralInfo extends Component {
</div>
</div>
<div className="save-info">
<Button>
<FormattedMessage id="Save" defaultMessage="Save"/>
</Button>
<Button className="custom-flat danger-flat">Cancel</Button>
<Button className="custom-raised primary">Save</Button>
</div>
</form>
</Row>

@ -16,7 +16,6 @@
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
class PackageManager extends Component {
@ -26,7 +25,7 @@ class PackageManager extends Component {
}
render() {
return(
return (
<div id="package-mgt-content">
</div>

@ -92,7 +92,9 @@ class CreateRelease extends Component {
</p>
</div>
<div>
<Button id="create-release-btn" onClick={this.showUploadArtifacts}>Create a {channel} Release</Button>
<Button id="create-release-btn" onClick={this.showUploadArtifacts}>Create
a {channel}
Release</Button>
</div>
</div>
</Row>

@ -41,7 +41,8 @@ class UploadPackage extends Component {
<a onClick={this.handleBack}>{"<-"}</a>
<span id="create-release-header">
<strong>
<FormattedMessage id="New.Release.For" defaultMessage="New Release For"/> {selectedChannel}
<FormattedMessage id="New.Release.For"
defaultMessage="New Release For"/> {selectedChannel}
</strong>
</span>
</div>

@ -47,24 +47,32 @@ class ApplicationView extends Component {
}
render() {
const platform = this.state.application;
console.log(platform);
if (this.state.application.length === 0) {
return <div/>
} else {
const app = this.state.application;
console.log(app);
return (
<div id="application-view-content">
<div id="application-view-row">
<Row>
<Col>
<div id="app-icon">
{/*TODO: Remove this*/}
<img
className="app-view-image"
src={app.icon}
/>
</div>
</Col>
<Col>
<Row>
<span><strong>Facebook</strong></span>
<p className="app-view-field">{app.name}</p>
</Row>
<Row>
<span className="app-updated-date">Last updated on 2017-09-23</span>
<span className="app-updated-date app-view-text">
<FormattedMessage id="Last.Updated"
defaultMessage="Last.Updated"/> {app.modifiedAt}</span>
</Row>
</Col>
</Row>
@ -72,7 +80,9 @@ class ApplicationView extends Component {
<div id="application-view-row">
<Row>
<Col>
<span className="app-install-count">2k Installs</span>
<span className="app-install-count app-view-text">
2k <FormattedMessage id="Installs" defaultMessage="Installs"/>
</span>
</Col>
</Row>
<Row>
@ -83,7 +93,11 @@ class ApplicationView extends Component {
<i className="fw fw-star"></i>
</Col>
<Col>
<a href="#">View in Store</a>
<p className="app-view-text">
<a href="#">
<FormattedMessage id="View.In.Store" defaultMessage="View.In.Store"/>
</a>
</p>
</Col>
</Row>
</div>
@ -91,47 +105,50 @@ class ApplicationView extends Component {
<div id="application-view-row">
<Row>
<Col>
<span><strong>
<p className="app-view-field">
<FormattedMessage id="Description" defaultMessage="Description"/>:
</strong></span>
</p>
</Col>
<Col>
<p>sdfjlkdsjfsjdfjsdf sfjdslkjfdsflkjdsfslkdjfl j</p>
<p className="app-view-text">{app.description}</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<p className="app-view-field">
<FormattedMessage id="Tags" defaultMessage="Tags"/>:
</strong></span>
</p>
</Col>
<Col>
<p>[list of tags...]</p>
<p className="app-view-text">[list of tags...]</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<p className="app-view-field">
<FormattedMessage id="Release" defaultMessage="Release"/>:
</strong></span>
</p>
</Col>
<Col>
<p>Production</p>
<p className="app-view-text">Production</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>
<p className="app-view-field">
<FormattedMessage id="Version" defaultMessage="Version"/>:
</strong></span>
</p>
</Col>
<Col>
<p>v1.0</p>
<p className="app-view-text">v1.0</p>
</Col>
</Row>
</div>
</div>
);
}
}
}

@ -0,0 +1,123 @@
/*
* 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 {Button, Col, Collapse, Row} from "reactstrap";
import {FormattedMessage} from "react-intl";
/**
* Platform component.
* In Platform listing, this component will be displayed as cards.
* */
class Platform extends Component {
constructor() {
super();
this.unFold = this.unFold.bind(this);
this.state = {
isOpen: false
}
}
unFold() {
let isOpen = this.state.isOpen;
this.setState({isOpen: !isOpen})
}
render() {
const {platform} = this.props;
return (
<div className="platform-content">
<Row>
<Col>
<div className="platform-text-container">
<p className="app-view-field">{platform.name}</p>
<p className="app-view-text">{platform.enabled ? "Active" : "Disabled"}</p>
</div>
</Col>
<Col>
<div className="platform-icon-container">
<p className="platform-icon-letter">{platform.name.charAt(0)}</p>
</div>
</Col>
</Row>
<Row>
<div className="platform-content-footer">
<Button className="custom-flat grey">{platform.enabled ?
<FormattedMessage id="Disable" defaultMessage="Disable"/> :
<FormattedMessage id="Activate" defaultMessage="Activate"/>}
</Button>
<Button className="custom-flat grey">
<FormattedMessage id="Share.With.Tenants" defaultMessage="Share.With.Tenants"/>
</Button>
<Button className="custom-flat grey circle-button" onClick={this.unFold}>
{this.state.isOpen ? <i className="fw fw-up"></i> : <i className="fw fw-down"></i>}
</Button>
</div>
</Row>
<div className="platform-content-more-outer">
<Row>
<Col>
<Collapse isOpen={this.state.isOpen}>
<div className="platform-content-more">
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="Description" defaultMessage="Description"/>
</p>
</Col>
<Col><p className="app-view-text">{platform.description}</p></Col>
</Row>
<Row>
<Col>
<p className="app-view-field">
<FormattedMessage id="File.Based" defaultMessage="File.Based"/>
</p>
</Col>
<Col>
<p className="app-view-text">{platform.fileBased ?
<FormattedMessage id="Yes" defaultMessage="Yes"/>
: <FormattedMessage id="No" defaultMessage="No"/>}</p>
</Col>
</Row>
<Row>
<Col><p className="app-view-field">
<FormattedMessage id="Tags" defaultMessage="Tags"/>
</p></Col>
<Col>
<p className="app-view-text">
{platform.tags.length > 0 ?
platform.tags :
<FormattedMessage
id="No.Platform.Tags"
defaultMessage="No.Platform.Tags"/>
}
</p>
</Col>
</Row>
</div>
</Collapse>
</Col>
</Row>
</div>
</div>
);
}
}
export default Platform;

@ -15,416 +15,69 @@
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import Chip from 'material-ui/Chip';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import Toggle from 'material-ui/Toggle';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import IconButton from 'material-ui/IconButton';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
import PlatformMgtApi from '../../api/platformMgtApi';
import Clear from 'material-ui/svg-icons/content/clear';
import {GridList, GridTile} from 'material-ui/GridList';
import Close from 'material-ui/svg-icons/navigation/close';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import AddCircleOutline from 'material-ui/svg-icons/content/add-circle-outline';
import {Button, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader} from "reactstrap";
import {FormattedMessage} from "react-intl";
/**
* Platform Create component.
* Contains following components:
* * Platform Name
* * Platform Description
* * Platform Icon
* * Whether the platform needs an app to be installed.
* * Whether the platform is enabled by default.
* * Whether the platform is shared with tenants.
* Platform view component.
* */
class PlatformCreate extends Component {
constructor() {
super();
this.onCreatePlatform = this.onCreatePlatform.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.addProperty = this.addProperty.bind(this);
this.addTags = this.addTags.bind(this);
this.clearForm = this.clearForm.bind(this);
this.onPropertySelect = this.onPropertySelect.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.removeIcon = this.removeIcon.bind(this);
this.onTextChange = this.onTextChange.bind(this);
this.renderChip = this.renderChip.bind(this);
this.removeProperty = this.removeProperty.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.state = {
tags: [],
defValue: "",
enabled: true,
allTenants: false,
files: [],
platformProperties: [],
selectedProperty: 0,
name: "",
description: "",
property: "",
icon: [],
identifier: "",
propertyTypes: [
{key: 0, value: 'String'},
{key: 1, value: 'Number'},
{key: 2, value: 'Boolean'},
{key: 3, value: 'File'}]
};
}
/**
* Handles toggle button actions.
* One method is used for all the toggle buttons and, each toggle is identified by the id.
* */
handleToggle(event) {
switch (event.target.id) {
case "enabled" : {
let enabled = this.state.enabled;
this.setState({enabled: !enabled});
break;
}
case "tenant" : {
let allTenants = this.state.allTenants;
this.setState({allTenants: !allTenants});
break;
}
}
}
/**
* Triggers the onChange action on property type selection.
* */
onPropertySelect(event, index, value) {
console.log(this.state.propertyTypes[value]);
this.setState({selectedProperty: value});
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleTagDelete(key) {
this.chipData = this.state.tags;
const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
}
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""});
open: false
}
}
/**
* Creates Chip array from state.tags.
* */
renderChip(data) {
return (
<Chip
key={data.key}
onRequestDelete={() => this.handleTagDelete(data.key)}
style={this.styles.chip}
>
{data.value}
</Chip>
);
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Remove the selected property from the property list.
* */
removeProperty(property) {
let properties = this.state.platformProperties;
properties.splice(properties.indexOf(property), 1);
this.setState({platformProperties: properties});
componentWillReceiveProps(props, nextprops) {
this.setState({open: props.open})
}
/**
* Add a new platform property.
* */
addProperty() {
let property = this.state.property;
let selected = this.state.selectedProperty;
this.setState({
platformProperties:
this.state.platformProperties.concat([
{
key: property,
value: this.state.propertyTypes[selected].value
}]),
property: "",
selectedProperty: 0
});
componentWillMount() {
this.setState({open: this.props.open});
}
/**
* Triggers in onChange event of text fields.
* Text fields are identified by their ids and the value will be persisted in the component state.
* */
onTextChange(event, value) {
let property = this.state.property;
let name = this.state.name;
let description = this.state.description;
let identifier = this.state.identifier;
switch (event.target.id) {
case "name": {
name = value;
this.setState({name: name});
break;
}
case "description": {
description = value;
this.setState({description: description});
break;
}
case "property": {
property = value;
this.setState({property: property});
break;
}
case "identifier": {
identifier = value;
this.setState({identifier: identifier});
}
}
};
/**
* Create platform object and call the create platform api.
* */
onCreatePlatform(event) {
//Call the platform create api.
event.preventDefault();
let platform = {};
platform.identifier = this.state.identifier;
platform.name = this.state.name;
platform.description = this.state.description;
platform.tags = this.state.tags;
platform.properties = this.state.platformProperties;
platform.icon = this.state.icon;
platform.enabled = this.state.enabled;
platform.allTenants = this.state.allTenants;
platform.defaultTenantMapping = true;
PlatformMgtApi.createPlatform(platform);
}
/**
* Remove the uploaded icon.
* */
removeIcon(event) {
event.preventDefault();
this.setState({icon: []});
}
/**
* Clears the user entered values in the form.
* */
clearForm(event) {
event.preventDefault();
this.setState({
enabled: true,
allTenants: false,
files: [],
platformProperties: [],
selectedProperty: 0,
name: "",
description: "",
property: "",
})
onCancelClick() {
this.setState({open: false})
}
render() {
const {
platformProperties,
allTenants,
enabled,
selectedProperty,
propertyTypes,
name,
tags,
defValue,
description,
identifier,
property
} = this.state;
return (
<div className="middle createplatformmiddle">
<Card>
<CardTitle title="Create Platform"/>
<CardActions>
<div className="createplatformcardaction">
<form>
<TextField
hintText="Unique Identifier for Platform."
id="identifier"
floatingLabelText="Identifier*"
floatingLabelFixed={true}
value={identifier}
onChange={this.onTextChange}
/>
<br/>
<TextField
hintText="Enter the Platform Name."
id="name"
floatingLabelText="Name*"
floatingLabelFixed={true}
value={name}
onChange={this.onTextChange}
/>
<br/>
<TextField
id="description"
hintText="Enter the Platform Description."
floatingLabelText="Description*"
floatingLabelFixed={true}
multiLine={true}
rows={2}
value={description}
onChange={this.onTextChange}
/>
<br/>
<br/>
<Toggle
id="tenant"
label="Shared with all Tenants"
labelPosition="right"
onToggle={this.handleToggle}
toggled={allTenants}
/>
<br/>
<Toggle
id="enabled"
label="Enabled"
labelPosition="right"
onToggle={this.handleToggle}
toggled={enabled}
/>
<br/>
<TextField
id="tags"
hintText="Enter Platform tags.."
floatingLabelText="Tags*"
floatingLabelFixed={true}
value={defValue}
onChange={this.handleTagChange}
onKeyPress={this.addTags}
/>
<br/>
<div className="createPlatformTagWrapper">
{tags.map(this.renderChip, this)}
</div>
<br/>
<div>
<p className="createplatformproperties">Platform Properties</p>
<div id="property-container">
{platformProperties.map((p) => {
return <div key={p.key}>{p.key} : {p.value}
<IconButton onClick={this.removeProperty.bind(this, p)}>
<Close className="createplatformpropertyclose"/>
</IconButton>
</div>
})}
</div>
<div className="createplatformproperty">
<TextField
id="property"
hintText="Property Name"
floatingLabelText="Platform Property*"
floatingLabelFixed={true}
value={this.state.property}
onChange={this.onTextChange}
/> <em/>
<SelectField
className="createplatformpropertyselect"
floatingLabelText="Property Type"
value={selectedProperty}
floatingLabelFixed={true}
onChange={this.onPropertySelect}>
{propertyTypes.map((type) => {
return <MenuItem key={type.key}
value={type.key}
primaryText={type.value}/>
})}
</SelectField>
<IconButton onClick={this.addProperty}>
<AddCircleOutline/>
</IconButton>
<br/>
</div>
</div>
<div>
<p className="createplatformiconp">Platform Icon*:</p>
<GridList className="createplatformicon" cols={1.1}>
{this.state.icon.map((tile) => (
<GridTile
key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this.removeIcon}>
<Clear/>
</IconButton>}>
<img src={tile.preview}/>
</GridTile>
))}
{this.state.icon.length === 0 ?
<Dropzone
className="createplatformdropzone"
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {
this.setState({icon, rejected})
}}
>
<p className="createplatformdropzonep">+</p>
</Dropzone> : <div/>}
</GridList>
</div>
<br/>
<RaisedButton
primary={true} label="Create"
onClick={this.onCreatePlatform}/>
<FlatButton label="Cancel" onClick={this.clearForm}/>
</form>
</div>
</CardActions>
</Card>
<Modal isOpen={this.state.open} toggle={this.toggle} id="platform-create-modal" backdrop={'static'}>
<ModalHeader>
<FormattedMessage id="Create.Platform" defaultMessage="Create.Platform"/>
</ModalHeader>
<ModalBody>
<FormGroup>
<Label for="platform-name">
<FormattedMessage id="Name" defaultMessage="Name"/>*
</Label>
<Input required type="text" name="appName" id="platform-name"/>
</FormGroup>
<FormGroup>
<Label for="platform-description">
<FormattedMessage id="Description" defaultMessage="Description"/>*
</Label>
<Input required type="textarea" name="appName" id="platform-description"/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button className="custom-flat danger-flat" onClick={this.onCancelClick}>
<FormattedMessage id="Cancel" defaultMessage="Cancel"/>
</Button>
<Button className="custom-raised primary">
<FormattedMessage id="Create" defaultMessage="Create"/>
</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
PlatformCreate.prototypes = {};
export default PlatformCreate;

@ -15,145 +15,63 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import TextField from 'material-ui/TextField';
import AuthHandler from "../../api/authHandler";
import DataTable from '../UIComponents/DataTable/DataTable';
import {Button, Col, Row} from "reactstrap";
import Platform from "./Platform";
import PlatformMgtApi from "../../api/platformMgtApi";
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import AuthHandler from "../../api/authHandler";
import PlatformCreate from "./PlatformCreate";
/**
* 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.
* Platform view component.
* */
class PlatformListing extends Component {
constructor() {
super();
this.setPlatforms = this.setPlatforms.bind(this);
this.onPlatformCreateClick = this.onPlatformCreateClick.bind(this);
this.state = {
platforms: [],
asc: true
};
openModal: false
}
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "platformName",
data_type: String,
sortable: true,
label: "Platform Name",
sort: this.sortData
},
{
data_id: "enabled",
data_type: String,
sortable: false,
label: "Enabled"
},
{
data_id: "fileBased",
data_type: String,
sortable: false,
label: "File Based"
}
];
componentDidMount() {
let platformsPromise = PlatformMgtApi.getPlatforms();
platformsPromise.then(
response => {
let platforms = this.setPlatforms(response.data);
this.setState({platforms: platforms});
}
).catch(
err => {
componentWillMount() {
PlatformMgtApi.getPlatforms().then(response => {
console.log(response);
this.setState({platforms: response.data});
}).catch(err => {
AuthHandler.unauthorizedErrorHandler(err);
}
)
}
/**
* Create platform objects from the response which can be displayed in the table.
* */
setPlatforms(platforms) {
let tmpPlatforms = [];
for (let index in platforms) {
let platform = {};
platform.id = platforms[index].identifier;
platform.platformName = platforms[index].name;
platform.enabled = platforms[index].enabled.toString();
platform.fileBased = platforms[index].fileBased.toString();
tmpPlatforms.push(platform)
}
return tmpPlatforms;
}
/**
* Handles the search action.
* When typing in the search bar, this method will be invoked.
* */
searchApplications(word) {
let searchedData = [];
})
}
/**
* Handles sort data function and toggles the asc state.
* asc: true : sort in ascending order.
* */
sortData() {
let isAsc = this.state.asc;
let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse();
this.setState({data: datas, asc: !isAsc});
}
compare(a, b) {
if (a.applicationName < b.applicationName)
return -1;
if (a.applicationName > b.applicationName)
return 1;
return 0;
}
onRowClick(id) {
//TODO: Remove this
console.log(id)
onPlatformCreateClick() {
this.setState({openModal: true});
}
render() {
return (
<div className='middle listingplatformmiddle'>
<Card className='listingplatformcard'>
<TextField hintText="Search" onChange={this.searchApplications.bind(this)}
className='listingplatformsearch'/>
<CardTitle title="Platforms" className='listingplatformTitle'/>
<CardActions>
</CardActions>
<DataTable
headers={this.headers}
data={this.state.platforms}
handleRowClick={this.onRowClick.bind(this)}
noDataMessage={{type: 'button', text: 'Create Platform'}}/>
</Card>
<div id="platform-listing">
<Row>
<div className="create-platform">
<Button className="custom-flat grey" onClick={this.onPlatformCreateClick}>
<i className="fw fw-add"></i>Create Platform
</Button>
</div>
</Row>
<Row>
<div id="platform-list">
{this.state.platforms.map(platform => {
return (
<Platform key={platform.identifier} platform={platform}/>
)
})}
</div>
</Row>
<PlatformCreate open={this.state.openModal}/>
</div>
);
}
}
PlatformListing.propTypes = {};
export default withRouter(PlatformListing);
export default PlatformListing;

@ -18,7 +18,7 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
import './appImage.css';
/**
* Component for holding uploaded image.
@ -29,18 +29,6 @@ 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);
}
/**
@ -56,7 +44,7 @@ class AppImage extends Component {
const {image, imageId} = this.props;
return (
<div className="image-container" style={this.props.imageStyles}>
<img src={image} className="image" id={imageId}/>
<img src={image} style={{width: '100%'}} className="image" id={imageId}/>
<div className="btn-content">
<i className="close-btn" id={imageId} onClick={this.removeImage}>X</i>
</div>

@ -16,35 +16,38 @@
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
import './chip.css';
class Chip extends Component {
constructor() {
super();
this.scriptId = "chip";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
this.onDeleteClick = this.onDeleteClick.bind(this);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
onDeleteClick() {
this.props.onDelete(this.props.content.key);
}
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 className="chip-content">
<div className="chip-text">{this.props.content.value}</div>
<div className="chip-close-btn" onClick={this.onDeleteClick}>
<i className="fw fw-error"></i>
</div>
</div>
</div>
)
}
}
Chip.propTypes = {
onDelete: PropTypes.func,
content: PropTypes.object
};
export default Chip;

@ -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.
*/
/*
* Chip component with the material design specs.
* Chip with a close button.
* (-12px-{text (Roboto-Regular 13px)}-4px-{close button}-4px-)
*/
.chip {
margin-right: 5px;
height: 32px;
background-color: #f1f1f1;
border-radius: 16px;
border: 10px;
}
.chip-content {
display: flex;
margin: 4px 4px 4px 12px;
}
.chip-text {
height: 24px;
font-family: "Roboto-Regular";
font-size: 13px;
font-weight: 400;
color: rgba(0, 0, 0, 0.87);
line-height: 20px;
padding-top: 4px;
margin-right: 4px;
user-select: none;
white-space: nowrap;
}
.chip-close-btn {
align-content: center;
padding-left: 4px;
padding-top: 1px;
display: inline-block;
height: 24px;
width: 24px;
user-select: none;
transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
cursor: pointer;
}
.chip-close-btn i {
color: #a9a9a9;
color: rgba(0, 0, 0, 0.258824);
fill: rgba(0, 0, 0, 0.258824);
}
.chip-close-btn i:hover {
color: #959595;
}

@ -16,13 +16,7 @@
* 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.
@ -53,8 +47,6 @@ class DataTable extends Component {
constructor() {
super();
this.handleRowClick = this.handleRowClick.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = {
data: [],
headers: [],
@ -62,92 +54,21 @@ class DataTable extends Component {
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}
/>
<div className="data-table">
{this.props.children}
</div>
)
})}
</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
};
DataTable.prototypes = {};
export default DataTable;

@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react';
import FlatButton from 'material-ui/FlatButton';
import {TableHeaderColumn} from 'material-ui/Table';
import {Col, Row} from "reactstrap";
/**
* Data Table header component.
@ -55,33 +56,25 @@ class DataTableHeader extends Component {
}
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.
/*margin-top: 30px
* margin-bottom: 10px
* */
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>
<Row className="data-table-header">
{this.props.headers.map(header => {
let headerStyle = header.size;
return <Col className={headerStyle}>{header.label}</Col>
})}
</Row>
);
}
}
DataTableHeader.prototypes = {
header: PropTypes.object
headers: PropTypes.array
};
export default DataTableHeader;

@ -23,6 +23,7 @@ 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';
import {Row} from "reactstrap";
/**
@ -63,43 +64,17 @@ class DataTableRow extends Component {
handleBtnClick(event) {
event.stopPropagation();
console.log(event.target['id'])
this.props.handleButtonClick(event.target['id']);
this.props.onAppEditClick(event.target['id']);
}
render() {
const {dataItem} = this.state;
//height: 62px
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>)
}
<Row className="data-table-row">
})}
<TableRowColumn
className="datatableRowColumn"
key={dataItem.id}
>
<IconButton id={dataItem.id} onClick={this.handleBtnClick.bind(this)}>
<Create id={dataItem.id}/>
</IconButton>
</TableRowColumn>
</TableRow>
</Row>
);
}
}

@ -0,0 +1,80 @@
/*
* 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';
import {Col, Row} from "reactstrap";
/**
* Data Table header component.
* This component creates the header elements of the table.
* */
class HeaderCell 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() {
/*margin-top: 30px
* margin-bottom: 10px
* */
return (
<Row className="data-table-header">
{this.props.headers.map(header => {
let headerStyle = header.size;
return <Col className={headerStyle}>{header.label}</Col>
})}
</Row>
);
}
}
DataTableHeader.prototypes = {
headers: PropTypes.array
};
export default HeaderCell;

@ -18,7 +18,8 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme'
import './drawer.css';
import {Row} from "reactstrap";
/**
* Custom React component for Application View.
@ -28,18 +29,6 @@ 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);
}
/**

@ -25,21 +25,19 @@
right: 0%;
background-color: #ffffff;
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 60px; /* Place content 60px from the top */
padding: 60px 0px 0 5px; /* 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 {
.drawer-close-btn {
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;
@ -49,9 +47,8 @@
margin-left: 50px;
}
.drawer-close-btn {
height: 40px;
width: 30px;
.drawer-close-btn i {
font-size: 14px;
}
.drawer-close-btn:hover {
@ -67,7 +64,8 @@
/* 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;}
.sidenav {
padding-top: 15px;
}
}

@ -18,29 +18,13 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
import './floatingButton.css';
/**
* 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);
}
@ -49,8 +33,10 @@ class FloatingButton extends Component {
let classes = 'btn-circle ' + this.props.className;
return (
<div className={classes} onClick={this.handleClick.bind(this)}>
<div className={classes + " btn-shade"}>
<i className="fw fw-add"></i>
</div>
</div>
)
}
}

@ -16,35 +16,39 @@
* under the License.
*/
/*
* Material design based Floating button.
*/
.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);
text-align: center;
box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px
}
.btn-circle:hover {
cursor: pointer;
}
.btn-shade:focus {
background-color: rgba(0, 0, 0, .12);
}
.btn-circle i {
position: absolute;
margin-top: 37%;
margin-left: 37%;
height: 24px;
width: 24px;
font-size: 18px;
padding: 3px;
margin-top: 16px
}
.small {
height: 50px;
width: 50px;
height: 56px;
width: 56px;
}
.medium {
height: 100px;
width: 100px;
}
.btn-circle:hover {
cursor: pointer;
background-color: rgb(255, 93, 2);
}
.primary {
background-color: blue;
}

@ -18,9 +18,9 @@
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import './imageUploader.css';
import Dropzone from "react-dropzone";
import {Row} from "reactstrap";
import Theme from '../../../theme';
class ImageUploader extends Component {
@ -29,19 +29,7 @@ class ImageUploader extends Component {
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) {

@ -0,0 +1,59 @@
/*
* 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 Axios from 'axios';
const imageLocation = "/images/";
class Logo extends Component {
constructor() {
super();
this.state = {
image: ""
}
}
componentWillMount() {
let url = imageLocation + this.props.image_name;
Axios.get(url, {responseType: 'arraybuffer'}).then(
response => {
let image = "data:image/jpeg;base64," + new Buffer(response.data, 'binary').toString('base64');
this.setState({image: image});
}
).catch(err => {
console.log(err);
});
}
render() {
return (
<img className={this.props.className} src={this.state.image} />
)
}
}
Logo.prototypes = {
className: PropTypes.string,
image_name: PropTypes.string
};
export default Logo;

@ -18,23 +18,11 @@
import React, {Component} from 'react';
import {Col, Row} from "reactstrap";
import Theme from '../../../theme'
import './notification.css';
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() {

@ -19,23 +19,10 @@
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() {

@ -17,26 +17,10 @@
*/
import React, {Component} from 'react';
import Theme from '../../../theme';
import './switch.css';
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 (

@ -20,7 +20,8 @@ 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';
import {Button, Col, Form, FormGroup, Input, Label, Row} from 'reactstrap';
import Logo from "../../UIComponents/Logo/Logo";
/**
* The Login Component.
@ -39,7 +40,8 @@ class Login extends Component {
userName: "",
password: "",
rememberMe: true,
errors: {}
errors: {},
loginError: ""
}
}
@ -120,7 +122,12 @@ class Login extends Component {
loginPromis.then(response => {
console.log(AuthHandler.getUser());
this.setState({isLoggedIn: AuthHandler.getUser()});
})
}).catch(
err => {
console.log(err);
this.setState({loginError: err});
}
);
}
}
@ -130,33 +137,51 @@ class Login extends Component {
return (
<div id="login-container">
{/*TODO: Style the components.*/}
<Card id="login-card">
<CardBlock>
<CardTitle>WSO2 IoT APP Publisher</CardTitle>
<div id="login-card">
<div id="login-card-content">
<Row className="login-header">
<Col>
<Logo className="login-header-logo" image_name="logo.png"/>
</Col>
<Col>
<p className="login-header-title">IoT APP Publisher</p>
</Col>
</Row>
<Row className="login-form">
<Col>
<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"
<FormGroup>
<Label for="userName">User Name:</Label>
<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"
<FormGroup>
<Label for="password">Password:</Label>
<Input
valid={false}
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>
<Button
type="submit"
className="custom-raised login-btn primary"
>
Login
</Button>
</FormGroup>
</Form>
</CardBlock>
</Card>
</Col>
</Row>
</div>
</div>
</div>);
} else {
return (

@ -21,7 +21,7 @@ import Publisher 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 {addLocaleData, defineMessages, IntlProvider} from 'react-intl';
import Axios from 'axios';
import Constants from './common/constants';
import Configuration from './common/configuration';
@ -45,7 +45,7 @@ function loadPublisher() {
registerServiceWorker();
}).catch(error => {
addLocaleData(require('react-intl/locale-data/en'));
let defaultLocale = axios.create({
let defaultLocale = Axios.create({
baseURL: Configuration.hostConstants.baseURL + "/" + Configuration.hostConstants.appContext + "/locales"
+ Constants.defaultLocale + ".json"
}).get();

@ -16,6 +16,7 @@
* under the License.
*/
var path = require('path');
import '!!style-loader!css-loader!src/css/font-wso2.css';
const config = {
entry: {

Loading…
Cancel
Save