mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Authentication handling initial impl and code formatting according to https://github.com/airbnb/javascript/tree/master/react#basic-rules.
This commit is contained in:
parent
b3d2abf99e
commit
7ff5823e6d
@ -18,6 +18,7 @@
|
||||
|
||||
import './App.scss';
|
||||
import React, {Component} from 'react';
|
||||
import AuthHandler from './api/authHandler';
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom'
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
@ -54,12 +55,19 @@ class Base extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
user: "s"
|
||||
user: null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let user = AuthHandler.getUser();
|
||||
|
||||
if (user) {
|
||||
if (!AuthHandler.isTokenExpired()) {
|
||||
this.setState({user: user});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -67,7 +75,7 @@ class Base extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.user) {
|
||||
if (this.state.user !== null) {
|
||||
console.log("Have User.");
|
||||
return (
|
||||
<div className="container">
|
||||
|
||||
@ -1,45 +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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Axios from 'axios';
|
||||
|
||||
/**
|
||||
* Handles all tasks related to Authentication and Authorization.
|
||||
* Generate access tokens, verify the user has necessary permissions etc.
|
||||
* */
|
||||
class AuthHandler {
|
||||
|
||||
/**
|
||||
* Generate client id and client secret to generate access tokens.
|
||||
* */
|
||||
login(userName, password) {
|
||||
Axios.post("https://localhost:9443/auth/application-mgt/v1.0/auth/tokens?userName=admin&password=admin").then()
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
|
||||
}
|
||||
|
||||
getloggedInUser() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -1,120 +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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Helper from './helpers/AppMgtApiHelpers';
|
||||
|
||||
/**
|
||||
* Application related apis
|
||||
* */
|
||||
export default class Endpoint{
|
||||
|
||||
/**
|
||||
* Api for create an application.
|
||||
* @param: applicationData: The application data object. This contains an object array of each step data from
|
||||
* application creation wizard.
|
||||
*
|
||||
* From that data array, the proper application object is created and send it to the api.
|
||||
* */
|
||||
static createApplication(applicationData) {
|
||||
|
||||
console.log("In application create application", applicationData);
|
||||
Helper.buildApplication(applicationData);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle application release process.
|
||||
* */
|
||||
static releaseApplication() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit created application.
|
||||
* @param applicationData: The modified application data.
|
||||
* */
|
||||
static editApplication(applicationData) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the created applications for the user.
|
||||
* */
|
||||
static getApplications() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific application.
|
||||
* @param appId : The application Id.
|
||||
* */
|
||||
static getApplication(appId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified application.
|
||||
* @param appId: The id of the application which is to be deleted.
|
||||
* */
|
||||
static deleteApplication(appId) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Platform related apis
|
||||
* */
|
||||
/**
|
||||
* Create a new Platform
|
||||
* @param platformData: The platform data object.
|
||||
* */
|
||||
static createPlatform(platformData) {
|
||||
// /api/application-mgt/v1.0/platforms/1.0.0/
|
||||
// {
|
||||
// identifier: "${platform_identifier}",
|
||||
// name: "New Platform",
|
||||
// description : "New Platform"
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available platforms
|
||||
* */
|
||||
static getPlatforms() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user specified platform
|
||||
* @param platformId: The identifier of the platform
|
||||
* */
|
||||
static getPlatform(platformId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified platform
|
||||
* @param platformId: The id of the platform which is to be deleted.
|
||||
* */
|
||||
static deletePlatform(platformId) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Axios from 'axios';
|
||||
import User from './data/user';
|
||||
import Utils from './data/utils';
|
||||
import Constants from "../common/constants";
|
||||
|
||||
/**
|
||||
* Handles all tasks related to Authentication and Authorization.
|
||||
* Generate access tokens, verify the user has necessary permissions etc.
|
||||
* */
|
||||
class AuthHandler {
|
||||
|
||||
/**
|
||||
* Sends a request to the auth handler endpoint (auth/application-mgt/v1.0/auth/login) and generate token pair.
|
||||
* @param userName: The user name of the user.
|
||||
* @param password: The user password.
|
||||
* @return Object: The response object from the axios post.
|
||||
* */
|
||||
static login(userName, password) {
|
||||
const headers = {"Content-type": "application/json"};
|
||||
let login_promise = Axios.post("https://localhost:9443/auth/application-mgt/v1.0/auth/login?userName=admin&password=admin",
|
||||
null, {headers: headers});
|
||||
|
||||
login_promise.then(response => {
|
||||
console.log(response);
|
||||
const userName = response.data.userName;
|
||||
const validityPeriod = response.data.expires_in; // In seconds
|
||||
const WSO2_IOT_TOKEN = response.data.access_token;
|
||||
const refreshToken = response.data.refresh_token;
|
||||
const clientId = response.data.application_info[0].consumerKey;
|
||||
const clientSecret = response.data.application_info[0].consumerSecret;
|
||||
|
||||
const user = new User(userName, clientId, clientSecret, validityPeriod);
|
||||
console.log(user);
|
||||
user.setAuthToken(WSO2_IOT_TOKEN, validityPeriod);
|
||||
AuthHandler.setUser(user);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return login_promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Persists the user object in browser's local storage.
|
||||
* @param user: The user object.
|
||||
* */
|
||||
static setUser(user) {
|
||||
if (!user instanceof User) {
|
||||
throw "Invalid user object";
|
||||
}
|
||||
localStorage.setItem(Constants.userConstants.WSO2_USER, JSON.stringify(user.toJson()));
|
||||
/* TODO: IMHO it's better to get this key (`wso2_user`) from configs */
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logged in user.
|
||||
* @return User: The logged in user object.
|
||||
* */
|
||||
static getUser() {
|
||||
const userData = localStorage.getItem(Constants.userConstants.WSO2_USER);
|
||||
const partialToken = Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
|
||||
|
||||
if (!(userData && partialToken)) {
|
||||
return null;
|
||||
}
|
||||
return User.fromJson(JSON.parse(userData));
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
|
||||
}
|
||||
|
||||
logout() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the access token is expired.
|
||||
* @return boolean: True if expired. False otherwise.
|
||||
* */
|
||||
static isTokenExpired() {
|
||||
const userData = AuthHandler.getUser().getAuthToken();
|
||||
return (Date.now() - userData._createdTime) > userData._expires;
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthHandler;
|
||||
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
import Utils from './utils'
|
||||
import Constants from '../../common/constants';
|
||||
/**
|
||||
* Represent an user logged in to the application, There will be allays one user per session and
|
||||
* this user details will be persist in browser localstorage.
|
||||
*/
|
||||
export default class User {
|
||||
constructor(name, clientId, clientSecret, validityPeriod) {
|
||||
if (User._instance) {
|
||||
return User._instance;
|
||||
}
|
||||
|
||||
this._userName = name;
|
||||
this._clientId = clientId;
|
||||
this._clientSecret = clientSecret;
|
||||
this._expires = validityPeriod;
|
||||
this._createdTime = Date.now();
|
||||
User._instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth scopes which are available for use by this user
|
||||
* @returns {Array} : An array of scopes
|
||||
*/
|
||||
get scopes() {
|
||||
return this._scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OAuth scopes available to be used by this user
|
||||
* @param {Array} newScopes : An array of scopes
|
||||
*/
|
||||
set scopes(newScopes) {
|
||||
Object.assign(this.scopes, newScopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JS accessible access token fragment from cookie storage.
|
||||
* @returns {String|null}
|
||||
*/
|
||||
getAuthToken() {
|
||||
return Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the JavaScript accessible access token segment in cookie storage
|
||||
* @param {String} newToken : Part of the access token which needs when accessing REST API
|
||||
* @param {Number} validityPeriod : Validity period of the cookie in seconds
|
||||
*/
|
||||
setAuthToken(newToken, validityPeriod) {
|
||||
Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN);
|
||||
Utils.setCookie(Constants.userConstants.PARTIAL_TOKEN, newToken, validityPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
checkPermission(type) {
|
||||
throw ("Not implemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide user data in JSON structure.
|
||||
* @returns {JSON} : JSON representation of the user object
|
||||
*/
|
||||
toJson() {
|
||||
return {
|
||||
name: this._userName,
|
||||
clientId: this._clientId,
|
||||
clientSecret: this._clientSecret,
|
||||
expires: this._expires
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* User utility method to create an user from JSON object.
|
||||
* @param {JSON} userJson : Need to provide user information in JSON structure to create an user object
|
||||
* @returns {User} : An instance of User(this) class.
|
||||
*/
|
||||
static fromJson(userJson) {
|
||||
|
||||
const _user = new User(userJson.name);
|
||||
_user._clientId = userJson.clientId;
|
||||
_user._clientSecret = userJson.clientSecret;
|
||||
_user._expires = userJson.expires;
|
||||
|
||||
console.log(_user);
|
||||
return _user;
|
||||
}
|
||||
}
|
||||
|
||||
User._instance = null; // A private class variable to preserve the single instance of a swaggerClient
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class for Publisher application
|
||||
*/
|
||||
class PublisherUtils {
|
||||
|
||||
/**
|
||||
* TODO: Remove this method one the initial phase is done, This is used to continue the API class until the login page is create
|
||||
* @returns {promise}
|
||||
*/
|
||||
// static autoLogin() {
|
||||
// let auth = new AuthManager();
|
||||
// return auth.authenticateUser('admin', 'admin');
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get JavaScript accessible cookies saved in browser, by giving the cooke name.
|
||||
* @param {String} name : Name of the cookie which need to be retrived
|
||||
* @returns {String|null} : If found a cookie with given name , return its value,Else null value is returned
|
||||
*/
|
||||
static getCookie(name) {
|
||||
let pairs = document.cookie.split(";");
|
||||
let cookie = null;
|
||||
for (let pair of pairs) {
|
||||
pair = pair.split("=");
|
||||
let cookie_name = pair[0].trim();
|
||||
let value = encodeURIComponent(pair[1]);
|
||||
if (cookie_name === name) {
|
||||
cookie = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a browser cookie given its name
|
||||
* @param {String} name : Name of the cookie which need to be deleted
|
||||
*/
|
||||
static delete_cookie(name) {
|
||||
document.cookie = name + '=; Path=' + "/" + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie with given name and value assigned to it. Cookies can be only set to the same origin,
|
||||
* which the script is running
|
||||
* @param {String} name : Name of the cookie which need to be set
|
||||
* @param {String} value : Value of the cookie, expect it to be URLEncoded
|
||||
* @param {number} validityPeriod : (Optional) Validity period of the cookie in seconds
|
||||
* @param {String} path : Path which needs to set the given cookie
|
||||
* @param {boolean} secured : secured parameter is set
|
||||
*/
|
||||
static setCookie(name, value, validityPeriod, path = "/", secured = true) {
|
||||
let expires = "";
|
||||
const securedDirective = secured ? "; Secure" : "";
|
||||
if (validityPeriod) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + validityPeriod * 1000);
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
|
||||
document.cookie = name + "=" + value + expires + "; path=" + path + securedDirective + validityPeriod
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an object returns whether the object is empty or not
|
||||
* @param {Object} object : Any JSON object
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isEmptyObject(object) {
|
||||
return Object.keys(object).length === 0 && object.constructor === Object
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default PublisherUtils;
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||
*
|
||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import Axios from 'axios';
|
||||
import AuthHandler from './authHandler';
|
||||
import Constants from '../common/constants';
|
||||
import Helper from './helpers/appMgtApiHelpers';
|
||||
|
||||
|
||||
|
||||
export default class Endpoint {
|
||||
|
||||
/* =================================================================
|
||||
* Application related apis
|
||||
* */
|
||||
|
||||
/**
|
||||
* Api for create an application.
|
||||
* @param: applicationData: The application data object. This contains an object array of each step data from
|
||||
* application creation wizard.
|
||||
*
|
||||
* From applicationData, the proper application object will be created and send it to the api.
|
||||
* */
|
||||
static createApplication(applicationData) {
|
||||
|
||||
let app = Helper.buildApplication(applicationData).application;
|
||||
let user = AuthHandler.getUser();
|
||||
console.log(user.idToken);
|
||||
const headers = {
|
||||
"Authorization": 'Bearer ' + user.getAuthToken(),
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
Axios.post(Constants.appManagerEndpoints.CREATE_APP, app, {headers: headers}).then(
|
||||
function (response) {
|
||||
console.log(response);
|
||||
}
|
||||
).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle application release process.
|
||||
* */
|
||||
static releaseApplication() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote the current state of the application.
|
||||
* @param appId: The uuid of the application which the state should be updated.
|
||||
* */
|
||||
static updateState(appId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next possible state, which the application can be promoted to.
|
||||
* @param appId: The application uuid.
|
||||
*/
|
||||
static getNextState(appId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit created application.
|
||||
* @param applicationData: The modified application data.
|
||||
* */
|
||||
static editApplication(applicationData) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the created applications for the user.
|
||||
* @return Object: The response object from the axios post.
|
||||
* */
|
||||
static getApplications() {
|
||||
let user = AuthHandler.getUser();
|
||||
console.log("Get all applications", user.getAuthToken());
|
||||
const headers = {
|
||||
"Authorization": 'Bearer ' + user.getAuthToken(),
|
||||
'Accept': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS, {headers: headers});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific application.
|
||||
* @param appId: The application Id.
|
||||
* */
|
||||
static getApplication(appId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified application.
|
||||
* @param appId: The id of the application which is to be deleted.
|
||||
* */
|
||||
static deleteApplication(appId) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* End of Application management apis.
|
||||
* =================================================================
|
||||
* */
|
||||
|
||||
/*
|
||||
* =================================================================
|
||||
* Platform related apis
|
||||
* */
|
||||
|
||||
/**
|
||||
* Create a new Platform
|
||||
* @param platformData: The platform data object.
|
||||
* */
|
||||
static createPlatform(platformData) {
|
||||
|
||||
const headers = {
|
||||
"Authorization": 'Bearer ' + AuthHandler.getUser().getAuthToken(),
|
||||
'Accept': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
Axios.post(Constants.platformManagerEndpoints.CREATE_PLATFORM, platformData, {headers: headers}).then(
|
||||
function (response) {
|
||||
console.log(response);
|
||||
}
|
||||
).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available platforms
|
||||
* */
|
||||
static getPlatforms() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user specified platform
|
||||
* @param platformId: The identifier of the platform
|
||||
* */
|
||||
static getPlatform(platformId) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete specified platform
|
||||
* @param platformId: The id of the platform which is to be deleted.
|
||||
* */
|
||||
static deletePlatform(platformId) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* End of Platform management apis.
|
||||
* =================================================================
|
||||
* */
|
||||
|
||||
}
|
||||
@ -26,24 +26,31 @@ export default class Helper {
|
||||
/**
|
||||
* Generate application object from form data passed.
|
||||
* @param appData: Application data from the application creation form.
|
||||
* @return {Object, Object}: The application object and the set of images related to the application.
|
||||
* */
|
||||
static buildApplication(appData) {
|
||||
|
||||
let application = {};
|
||||
let images = {};
|
||||
|
||||
for (var step in appData) {
|
||||
for (let step in appData) {
|
||||
let tmpData = appData[step].data.step;
|
||||
for (var prop in tmpData) {
|
||||
for (let prop in tmpData) {
|
||||
if (prop === 'banner' || prop === 'screenshots' || prop === 'icon') {
|
||||
images[prop] = tmpData[prop];
|
||||
} else if(prop === 'tags') {
|
||||
let tags = [];
|
||||
let tagsFromStep = tmpData[prop];
|
||||
for (let tag in tagsFromStep) {
|
||||
console.log(tag);
|
||||
tags.push(tagsFromStep[tag].value);
|
||||
}
|
||||
application[prop] = tags;
|
||||
} else {
|
||||
application[prop] = tmpData[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(application, images);
|
||||
return {application, images};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -16,16 +16,30 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
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';
|
||||
export default class Constants {
|
||||
|
||||
TOKEN_ENDPOINT = '/token';
|
||||
DYNAMIC_CLIENT_REGISTER_ENDPOINT = '/api-application-registration/register';
|
||||
static 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';
|
||||
|
||||
static TOKEN_ENDPOINT = '/token';
|
||||
static DYNAMIC_CLIENT_REGISTER_ENDPOINT = '/api-application-registration/register';
|
||||
|
||||
static appManagerEndpoints = {
|
||||
GET_ALL_APPS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/',
|
||||
CREATE_APP: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/',
|
||||
UPLOAD_IMAGES: '/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
|
||||
};
|
||||
|
||||
static platformManagerEndpoints = {
|
||||
CREATE_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0/'
|
||||
}
|
||||
|
||||
static userConstants = {
|
||||
WSO2_USER: 'wso2_user',
|
||||
PARTIAL_TOKEN: 'WSO2_IOT_TOKEN'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
appManagerEndpoints = {
|
||||
GET_ALL_APPS: '/api/application-mgt/v1.0/applications/1.0.0/',
|
||||
CREATE_APP: '/api/application-mgt/v1.0/applications/1.0.0/',
|
||||
UPLOAD_IMAGES: '/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import Endpoint from '../../api/Endpoints';
|
||||
import Endpoint from '../../api/endpoints';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import {Step1, Step2, Step3} from './CreateSteps';
|
||||
@ -67,7 +67,7 @@ class ApplicationCreate extends Component {
|
||||
/**
|
||||
* Handles next button click event.
|
||||
* */
|
||||
handleNext = () => {
|
||||
handleNext() {
|
||||
console.log("Handle Next");
|
||||
const {stepIndex} = this.state;
|
||||
this.setState({
|
||||
@ -79,7 +79,7 @@ class ApplicationCreate extends Component {
|
||||
/**
|
||||
* Handles form submit.
|
||||
* */
|
||||
handleSubmit = () => {
|
||||
handleSubmit() {
|
||||
console.log(this.state.stepData);
|
||||
Endpoint.createApplication(this.state.stepData);
|
||||
|
||||
@ -89,7 +89,7 @@ class ApplicationCreate extends Component {
|
||||
* Handles cancel button click event.
|
||||
* This will show a confirmation dialog to cancel the application creation process.
|
||||
* */
|
||||
handleCancel = () => {
|
||||
handleCancel() {
|
||||
this.setState({isDialogOpen: true});
|
||||
};
|
||||
|
||||
@ -97,7 +97,7 @@ class ApplicationCreate extends Component {
|
||||
* Handled [ < Prev ] button click.
|
||||
* This clears the data in the current step and returns to the previous step.
|
||||
* */
|
||||
handlePrev = () => {
|
||||
handlePrev() {
|
||||
const {stepIndex} = this.state;
|
||||
if (stepIndex > 0) {
|
||||
this.removeStepData();
|
||||
@ -108,7 +108,7 @@ class ApplicationCreate extends Component {
|
||||
/**
|
||||
* Saves form data in each step in to the state.
|
||||
* */
|
||||
setStepData = (step, data) => {
|
||||
setStepData(step, data) {
|
||||
console.log(step, data, this.state.stepData);
|
||||
let tmpStepData = this.state.stepData;
|
||||
tmpStepData.push({step: step, data: data});
|
||||
@ -119,7 +119,7 @@ class ApplicationCreate extends Component {
|
||||
/**
|
||||
* Remove the last data point
|
||||
* */
|
||||
removeStepData = () => {
|
||||
removeStepData() {
|
||||
let tempData = this.state.stepData;
|
||||
tempData.pop();
|
||||
this.setState({stepData: tempData});
|
||||
@ -129,7 +129,7 @@ class ApplicationCreate extends Component {
|
||||
* Handles the Yes button in app creation cancellation dialog.
|
||||
* Clears all the form data and reset the wizard.
|
||||
* */
|
||||
handleYes = () => {
|
||||
handleYes() {
|
||||
this.setState({finished: false, stepIndex: 0, stepData: [], isDialogOpen: false});
|
||||
};
|
||||
|
||||
@ -137,7 +137,7 @@ class ApplicationCreate extends Component {
|
||||
* Handles No button in app creation cancellation dialog.
|
||||
* Returns to the same step.
|
||||
* */
|
||||
handleNo = () => {
|
||||
handleNo() {
|
||||
this.setState({isDialogOpen: false});
|
||||
};
|
||||
|
||||
@ -153,21 +153,27 @@ class ApplicationCreate extends Component {
|
||||
getStepContent(stepIndex) {
|
||||
switch (stepIndex) {
|
||||
case 0:
|
||||
return <Step1 handleNext={this.handleNext}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}/>;
|
||||
return <Step1
|
||||
handleNext={this.handleNext}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}
|
||||
/>;
|
||||
case 1:
|
||||
return <Step2 handleNext={this.handleNext}
|
||||
handlePrev={this.handlePrev}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}/>;
|
||||
return <Step2
|
||||
handleNext={this.handleNext}
|
||||
handlePrev={this.handlePrev}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}
|
||||
/>;
|
||||
case 2:
|
||||
return <Step3 handleFinish={this.handleNext}
|
||||
handlePrev={this.handlePrev}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}/>;
|
||||
return <Step3
|
||||
handleFinish={this.handleNext}
|
||||
handlePrev={this.handlePrev}
|
||||
setData={this.setStepData}
|
||||
removeData={this.removeStepData}
|
||||
/>;
|
||||
default:
|
||||
return <div></div>;
|
||||
return <div/>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import EndPoint from '../../api/endpoints';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import DataTable from '../UIComponents/DataTable';
|
||||
@ -44,37 +45,37 @@ class ApplicationListing extends Component {
|
||||
data = [
|
||||
{
|
||||
id: Math.random(),
|
||||
applicationName:"Cne",
|
||||
platform:'Android',
|
||||
category:"Public",
|
||||
applicationName: "Cne",
|
||||
platform: 'Android',
|
||||
category: "Public",
|
||||
status: "Created"
|
||||
},
|
||||
{
|
||||
id: Math.random(),
|
||||
applicationName:"Gone",
|
||||
platform:'IOS',
|
||||
category:"Public",
|
||||
applicationName: "Gone",
|
||||
platform: 'IOS',
|
||||
category: "Public",
|
||||
status: "Created"
|
||||
},
|
||||
{
|
||||
id: Math.random(),
|
||||
applicationName:"Ane",
|
||||
platform:'Android',
|
||||
category:"Public",
|
||||
applicationName: "Ane",
|
||||
platform: 'Android',
|
||||
category: "Public",
|
||||
status: "Created"
|
||||
},
|
||||
{
|
||||
id: Math.random(),
|
||||
applicationName:"one",
|
||||
platform:'Android',
|
||||
category:"Public",
|
||||
applicationName: "one",
|
||||
platform: 'Android',
|
||||
category: "Public",
|
||||
status: "Created"
|
||||
},
|
||||
{
|
||||
id: Math.random(),
|
||||
applicationName:"one",
|
||||
platform:'Android',
|
||||
category:"Public",
|
||||
applicationName: "one",
|
||||
platform: 'Android',
|
||||
category: "Public",
|
||||
status: "Created"
|
||||
},
|
||||
];
|
||||
@ -84,19 +85,21 @@ class ApplicationListing extends Component {
|
||||
data_id: "image",
|
||||
data_type: "image",
|
||||
sortable: false,
|
||||
label: ""},
|
||||
label: ""
|
||||
},
|
||||
{
|
||||
data_id: "applicationName",
|
||||
data_type: "string",
|
||||
sortable: true,
|
||||
label: "Application Name",
|
||||
sort: this._sortData.bind(this)
|
||||
sort: this.sortData.bind(this)
|
||||
},
|
||||
{
|
||||
data_id: "platform",
|
||||
data_type: "image_array",
|
||||
sortable: false,
|
||||
label: "Platform"},
|
||||
label: "Platform"
|
||||
},
|
||||
{
|
||||
data_id: "category",
|
||||
data_type: "string",
|
||||
@ -125,36 +128,41 @@ class ApplicationListing extends Component {
|
||||
Theme.removeThemingScripts(this.scriptId);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let getApps = EndPoint.getApplications();
|
||||
getApps.then(response => {
|
||||
console.log(response);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the search action.
|
||||
* When typing in the search bar, this method will be invoked.
|
||||
* */
|
||||
_searchApplications(event, word) {
|
||||
searchApplications(event, word) {
|
||||
let searchedData;
|
||||
if (word){
|
||||
searchedData = this.data.filter((dataItem) => {
|
||||
return dataItem.applicationName.includes(word);
|
||||
});
|
||||
} else {
|
||||
searchedData = this.data;
|
||||
}
|
||||
if (word) {
|
||||
searchedData = this.data.filter((dataItem) => {
|
||||
return dataItem.applicationName.includes(word);
|
||||
});
|
||||
} else {
|
||||
searchedData = this.data;
|
||||
}
|
||||
|
||||
this.setState({data: searchedData}, console.log("Searched data ", this.state.data));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles sort data function and toggles the asc state.
|
||||
* asc: true : sort in ascending order.
|
||||
* */
|
||||
_sortData() {
|
||||
sortData() {
|
||||
let isAsc = this.state.asc;
|
||||
let datas = isAsc?this.data.sort(this._compare):this.data.reverse();
|
||||
let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse();
|
||||
this.setState({data: datas, asc: !isAsc});
|
||||
}
|
||||
|
||||
_compare(a, b) {
|
||||
compare(a, b) {
|
||||
if (a.applicationName < b.applicationName)
|
||||
return -1;
|
||||
if (a.applicationName > b.applicationName)
|
||||
@ -162,8 +170,8 @@ class ApplicationListing extends Component {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_onRowClick(id) {
|
||||
this.props.history.push("apps/"+id);
|
||||
onRowClick(id) {
|
||||
this.props.history.push("apps/" + id);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -171,18 +179,17 @@ class ApplicationListing extends Component {
|
||||
<div className="middle applicationListingMiddle">
|
||||
<Card className="applicationListingCard">
|
||||
<TextField hintText="Search" className="applicationListingSearch"
|
||||
onChange={this._searchApplications.bind(this)}/>
|
||||
onChange={this.searchApplications.bind(this)}/>
|
||||
<CardTitle title="Applications" className="applicationListTitle"/>
|
||||
<CardActions>
|
||||
|
||||
</CardActions>
|
||||
<DataTable headers={this.headers}
|
||||
data={this.state.data}
|
||||
handleRowClick={this._onRowClick.bind(this)}
|
||||
noDataMessage={{type: 'button', text: 'Create Application'}}/>
|
||||
<DataTable
|
||||
headers={this.headers}
|
||||
data={this.state.data}
|
||||
handleRowClick={this.onRowClick.bind(this)}
|
||||
noDataMessage={{type: 'button', text: 'Create Application'}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,11 +39,13 @@ import Theme from '../../../theme';
|
||||
class Step1 extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.platforms = [{identifier: 1}, {identifier: 2}, {identifier: 3}];
|
||||
this.stores = [{identifier: 5}, {identifier: 2}, {identifier: 3}];
|
||||
this.state = {
|
||||
finished: false,
|
||||
stepIndex: 0,
|
||||
store: 1,
|
||||
platform: 1,
|
||||
platform: 0,
|
||||
stepData: [],
|
||||
title: "",
|
||||
titleError: ""
|
||||
@ -65,17 +67,17 @@ class Step1 extends Component {
|
||||
/**
|
||||
* Invokes the handleNext function in Create component.
|
||||
* */
|
||||
_handleNext = () => {
|
||||
handleNext() {
|
||||
this.props.handleNext();
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist the current form data to the state.
|
||||
* */
|
||||
_setStepData() {
|
||||
var step = {
|
||||
setStepData() {
|
||||
let step = {
|
||||
store: this.state.store,
|
||||
platform: this.state.platform
|
||||
platform: this.platforms[this.state.platform]
|
||||
};
|
||||
this.props.setData("step1", {step: step});
|
||||
}
|
||||
@ -86,14 +88,14 @@ class Step1 extends Component {
|
||||
* Sets the data to the state.
|
||||
* Invokes the handleNext method of Create component.
|
||||
* */
|
||||
_handleClick() {
|
||||
this._setStepData();
|
||||
handleClick() {
|
||||
this.setStepData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers when changing the Platform selection.
|
||||
* */
|
||||
_onChangePlatform = (event, index, value) => {
|
||||
onChangePlatform(event, index, value) {
|
||||
console.log(value);
|
||||
this.setState({platform: value});
|
||||
};
|
||||
@ -101,17 +103,10 @@ class Step1 extends Component {
|
||||
/**
|
||||
* Triggers when changing the Store selection.
|
||||
* */
|
||||
_onChangeStore = (event, index, value) => {
|
||||
onChangeStore(event, index, value) {
|
||||
this.setState({store: value});
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers when user types on Title text field.
|
||||
* */
|
||||
_onChangeTitle = (event, value) => {
|
||||
this.setState({title: value});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
@ -122,30 +117,30 @@ class Step1 extends Component {
|
||||
floatingLabelText="Store Type*"
|
||||
value={this.state.store}
|
||||
floatingLabelFixed={true}
|
||||
onChange={this._onChangeStore.bind(this)}
|
||||
onChange={this.onChangeStore.bind(this)}
|
||||
>
|
||||
<MenuItem value={1} primaryText="Enterprise"/>
|
||||
<MenuItem value={2} primaryText="Public"/>
|
||||
</SelectField> <br/>
|
||||
<MenuItem value={0} primaryText="Enterprise"/>
|
||||
<MenuItem value={1} primaryText="Public"/>
|
||||
</SelectField>
|
||||
<br/>
|
||||
<SelectField
|
||||
floatingLabelText="Platform*"
|
||||
value={this.state.platform}
|
||||
floatingLabelFixed={true}
|
||||
onChange={this._onChangePlatform.bind(this)}
|
||||
onChange={this.onChangePlatform.bind(this)}
|
||||
>
|
||||
<MenuItem value={1} primaryText="Android"/>
|
||||
<MenuItem value={2} primaryText="iOS"/>
|
||||
<MenuItem value={{name: "Web", id:3}} primaryText="Web"/>
|
||||
<MenuItem value={0} primaryText="Android"/>
|
||||
<MenuItem value={1} primaryText="iOS"/>
|
||||
<MenuItem value={2} primaryText="Web"/>
|
||||
</SelectField>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<div className="nextButton">
|
||||
<RaisedButton
|
||||
label="Next >"
|
||||
primary={true}
|
||||
onClick={this._handleClick.bind(this)}
|
||||
onClick={this.handleClick.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -65,6 +65,7 @@ class Step2 extends Component {
|
||||
visibility: 0,
|
||||
description: "",
|
||||
screenshots: [],
|
||||
identifier: "",
|
||||
shortDescription: ""
|
||||
};
|
||||
this.scriptId = "application-create-step2";
|
||||
@ -86,19 +87,19 @@ class Step2 extends Component {
|
||||
* Clears the tags text field.
|
||||
* Chip gets two parameters: Key and value.
|
||||
* */
|
||||
_addTags(event) {
|
||||
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: ""});
|
||||
this.setState({tags, defValue: ""}, console.log(tags));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for tag.
|
||||
* */
|
||||
_handleTagChange(event) {
|
||||
handleTagChange(event) {
|
||||
let defaultValue = this.state.defValue;
|
||||
defaultValue = event.target.value;
|
||||
this.setState({defValue: defaultValue})
|
||||
@ -107,21 +108,21 @@ class Step2 extends Component {
|
||||
/**
|
||||
* Invokes the handleNext function in Create component.
|
||||
* */
|
||||
_handleNext() {
|
||||
handleNext() {
|
||||
let fields = [{name: "Title", value: this.state.title},
|
||||
{name: "Short Description", value: this.state.shortDescription},
|
||||
{name: "Description", value: this.state.description},
|
||||
{name: "Banner", value: this.state.banner},
|
||||
{name: "Screenshots", value: this.state.screenshots},
|
||||
{name: "Identifier", value: this.state.identifier},
|
||||
{name: "Icon", value: this.state.icon}];
|
||||
this._validate(fields);
|
||||
// this.props.handleNext();
|
||||
this.validate(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the handlePrev function in Create component.
|
||||
* */
|
||||
_handlePrev() {
|
||||
handlePrev() {
|
||||
this.props.handlePrev();
|
||||
}
|
||||
|
||||
@ -129,7 +130,7 @@ class Step2 extends Component {
|
||||
* Handles Chip delete function.
|
||||
* Removes the tag from state.tags
|
||||
* */
|
||||
_handleRequestDelete = (key) => {
|
||||
handleRequestDelete(key) {
|
||||
this.chipData = this.state.tags;
|
||||
const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key);
|
||||
this.chipData.splice(chipToDelete, 1);
|
||||
@ -139,18 +140,18 @@ class Step2 extends Component {
|
||||
/**
|
||||
* Creates Chip array from state.tags.
|
||||
* */
|
||||
_renderChip(data) {
|
||||
renderChip(data) {
|
||||
return (
|
||||
<Chip
|
||||
key={data.key}
|
||||
onRequestDelete={() => this._handleRequestDelete(data.key)}
|
||||
onRequestDelete={() => this.handleRequestDelete(data.key)}
|
||||
className="applicationCreateChip">
|
||||
{data.value}
|
||||
</Chip>
|
||||
);
|
||||
}
|
||||
|
||||
_onVisibilitySelect = (event, index, value) => {
|
||||
onVisibilitySelect(event, index, value) {
|
||||
console.log(value);
|
||||
let comp = <SelectField> <MenuItem value={0} primaryText="Public"/>
|
||||
<MenuItem value={1} primaryText="Roles"/>
|
||||
@ -167,7 +168,7 @@ class Step2 extends Component {
|
||||
/**
|
||||
* Validate the form.
|
||||
* */
|
||||
_validate(fields) {
|
||||
validate(fields) {
|
||||
let errors = {};
|
||||
let errorsPresent = false;
|
||||
fields.forEach(function (field) {
|
||||
@ -181,6 +182,15 @@ class Step2 extends Component {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Identifier': {
|
||||
if (field.value === "") {
|
||||
errors[field.name] = field.name + " is required!";
|
||||
errorsPresent = true;
|
||||
} else {
|
||||
errorsPresent = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Short Description': {
|
||||
if (field.value === "") {
|
||||
errors[field.name] = field.name + " is required!";
|
||||
@ -219,7 +229,7 @@ class Step2 extends Component {
|
||||
}
|
||||
case 'Screenshots': {
|
||||
if (field.value.length < 3) {
|
||||
errors[field.name] = "3 " +field.name + " are required!";
|
||||
errors[field.name] = "3 " + field.name + " are required!";
|
||||
errorsPresent = true;
|
||||
} else {
|
||||
errorsPresent = false;
|
||||
@ -229,41 +239,40 @@ class Step2 extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
console.log(errorsPresent);
|
||||
if (!errorsPresent) {
|
||||
this._setStepData();
|
||||
this.setStepData();
|
||||
} else {
|
||||
this.setState({errors: errors}, console.log(errors));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object with the current step data and persist in the parent.
|
||||
* */
|
||||
_setStepData() {
|
||||
setStepData() {
|
||||
let stepData = {
|
||||
tags: this.state.tags,
|
||||
icon: this.state.icon,
|
||||
title: this.state.title,
|
||||
name: this.state.name,
|
||||
tags: this.state.tags,
|
||||
banner: this.state.banner,
|
||||
category: this.state.category,
|
||||
category: this.categories[this.state.category],
|
||||
identifier: this.state.identifier,
|
||||
screenshots: this.state.screenshots,
|
||||
description: this.state.description,
|
||||
shortDescription: this.state.shortDescription
|
||||
};
|
||||
|
||||
this.props.setData("step2", {step: stepData});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set text field values to state.
|
||||
* */
|
||||
_onTextFieldChange(event, value) {
|
||||
onTextFieldChange(event, value) {
|
||||
let field = event.target.id;
|
||||
switch (field) {
|
||||
case "title": {
|
||||
this.setState({title: value});
|
||||
case "name": {
|
||||
this.setState({name: value});
|
||||
break;
|
||||
}
|
||||
case "shortDescription": {
|
||||
@ -274,30 +283,34 @@ class Step2 extends Component {
|
||||
this.setState({description: value});
|
||||
break;
|
||||
}
|
||||
case "identifier": {
|
||||
this.setState({identifier: value});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removed user uploaded banner.
|
||||
* */
|
||||
_removeBanner(event, d) {
|
||||
removeBanner(event, d) {
|
||||
console.log(event, d);
|
||||
this.setState({banner: []});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes uploaded icon.
|
||||
* */
|
||||
_removeIcon(event) {
|
||||
removeIcon(event) {
|
||||
this.setState({icon: []});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes selected screenshot.
|
||||
* */
|
||||
_removeScreenshot(event) {
|
||||
removeScreenshot(event) {
|
||||
console.log(event.target)
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
console.log(this.state.visibilityComponent);
|
||||
@ -306,13 +319,23 @@ class Step2 extends Component {
|
||||
<div>
|
||||
<div>
|
||||
<TextField
|
||||
id="title"
|
||||
hintText="Enter a title for your application."
|
||||
id="name"
|
||||
hintText="Enter a name for your application."
|
||||
errorText={this.state.errors["Title"]}
|
||||
floatingLabelText="Title*"
|
||||
floatingLabelText="Name*"
|
||||
floatingLabelFixed={true}
|
||||
onChange={this._onTextFieldChange.bind(this)}
|
||||
/><br/>
|
||||
onChange={this.onTextFieldChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
id="identifier"
|
||||
hintText="Unique Identifier for Application."
|
||||
errorText={this.state.errors["Identifier"]}
|
||||
floatingLabelText="Identifier*"
|
||||
floatingLabelFixed={true}
|
||||
onChange={this.onTextFieldChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
id="shortDescription"
|
||||
hintText="Enter a short description for your application."
|
||||
@ -321,9 +344,9 @@ class Step2 extends Component {
|
||||
floatingLabelFixed={true}
|
||||
multiLine={true}
|
||||
rows={2}
|
||||
onChange={this._onTextFieldChange.bind(this)}
|
||||
|
||||
/><br/>
|
||||
onChange={this.onTextFieldChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
id="description"
|
||||
errorText={this.state.errors["Description"]}
|
||||
@ -332,18 +355,20 @@ class Step2 extends Component {
|
||||
floatingLabelFixed={true}
|
||||
multiLine={true}
|
||||
rows={4}
|
||||
onChange={this._onTextFieldChange.bind(this)}
|
||||
/><br/>
|
||||
onChange={this.onTextFieldChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<SelectField
|
||||
floatingLabelText="Visibility*"
|
||||
value={this.state.visibility}
|
||||
floatingLabelFixed={true}
|
||||
onChange={this._onVisibilitySelect.bind(this)}
|
||||
onChange={this.onVisibilitySelect.bind(this)}
|
||||
>
|
||||
<MenuItem value={0} primaryText="Public"/>
|
||||
<MenuItem value={1} primaryText="Roles"/>
|
||||
<MenuItem value={2} primaryText="Devices"/>
|
||||
</SelectField><br/>
|
||||
</SelectField>
|
||||
<br/>
|
||||
<TextField
|
||||
id="tags"
|
||||
errorText={this.state.errors["tags"]}
|
||||
@ -351,11 +376,12 @@ class Step2 extends Component {
|
||||
floatingLabelText="Tags*"
|
||||
floatingLabelFixed={true}
|
||||
value={this.state.defValue}
|
||||
onChange={this._handleTagChange.bind(this)}
|
||||
onKeyPress={this._addTags.bind(this)}
|
||||
/><br/>
|
||||
<div className="applicationCreateWrapper">
|
||||
{this.state.tags.map(this._renderChip, this)}
|
||||
onChange={this.handleTagChange.bind(this)}
|
||||
onKeyPress={this.addTags.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<div style={this.styles.wrapper}>
|
||||
{this.state.tags.map(this.renderChip, this)}
|
||||
</div>
|
||||
<br/>
|
||||
<SelectField
|
||||
@ -364,7 +390,8 @@ class Step2 extends Component {
|
||||
floatingLabelFixed={true}
|
||||
>
|
||||
<MenuItem value={0} primaryText="Business"/>
|
||||
</SelectField> <br/>
|
||||
</SelectField>
|
||||
<br/>
|
||||
{/*Platform Specific Properties.*/}
|
||||
<div className="platformSpecificPropertyDiv">
|
||||
<p className="platformSpecificPropertyP">Platform Specific Properties</p>
|
||||
@ -375,50 +402,58 @@ class Step2 extends Component {
|
||||
<p className="applicationCreateBannerTitle">Banner*:</p>
|
||||
<GridList className="applicationCreateGrid" cols={1.1}>
|
||||
{this.state.banner.map((tile) => (
|
||||
<GridTile key={Math.floor(Math.random() * 1000)}
|
||||
title={tile.name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this._removeBanner.bind(this)}>
|
||||
<Clear />
|
||||
</IconButton>}>
|
||||
<img src={tile.preview}/></GridTile>
|
||||
<GridTile
|
||||
key={Math.floor(Math.random() * 1000)}
|
||||
title={tile.name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this.removeBanner.bind(this)}>
|
||||
<Clear/>
|
||||
</IconButton>}>
|
||||
<img src={tile.preview}/>
|
||||
</GridTile>
|
||||
))}
|
||||
{this.state.banner.length === 0 ?
|
||||
<Dropzone className="applicationCreateBannerDropZone" accept="image/jpeg, image/png"
|
||||
onDrop={(banner, rejected) => {
|
||||
this.setState({banner, rejected});
|
||||
}}>
|
||||
<p className="applicationCreateBannerp">+</p>
|
||||
</Dropzone> : <div />}
|
||||
|
||||
<Dropzone
|
||||
className="applicationCreateBannerDropZone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(banner, rejected) => {
|
||||
this.setState({banner, rejected});
|
||||
}}
|
||||
>
|
||||
<p className="applicationCreateBannerp">+</p>
|
||||
</Dropzone> : <div/>
|
||||
}
|
||||
</GridList>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<p className="applicationCreateScreenshotError">{this.state.errors["Screenshots"]}</p>
|
||||
<p className="applicationCreateScreenshotTitle">Screenshots*:</p>
|
||||
<GridList className = "applicationCreateScreenshotGrid" cols={1.1}>
|
||||
<GridList className="applicationCreateScreenshotGrid" cols={1.1}>
|
||||
{this.state.screenshots.map((file) => (
|
||||
<GridTile key={Math.floor(Math.random() * 1000)}
|
||||
title={file[0].name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this._removeScreenshot.bind(this)}>
|
||||
<Clear/>
|
||||
</IconButton>}>
|
||||
<GridTile
|
||||
key={Math.floor(Math.random() * 1000)}
|
||||
title={file[0].name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this.removeScreenshot.bind(this)}>
|
||||
<Clear/>
|
||||
</IconButton>}>
|
||||
<img src={file[0].preview}/></GridTile>
|
||||
))}
|
||||
{this.state.screenshots.length < 3 ?
|
||||
<Dropzone className="applicationCreateScreenshotDropZone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(screenshots, rejected) => {
|
||||
let tmpScreenshots = this.state.screenshots;
|
||||
tmpScreenshots.push(screenshots);
|
||||
this.setState({
|
||||
screenshots: tmpScreenshots});
|
||||
}}>
|
||||
<p className="applicationCreateScreenshotp">+</p>
|
||||
</Dropzone> : <div />}
|
||||
<Dropzone
|
||||
className="applicationCreateScreenshotDropZone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(screenshots, rejected) => {
|
||||
let tmpScreenshots = this.state.screenshots;
|
||||
tmpScreenshots.push(screenshots);
|
||||
this.setState({
|
||||
screenshots: tmpScreenshots
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="applicationCreateScreenshotp">+</p>
|
||||
</Dropzone> : <div/>}
|
||||
</GridList>
|
||||
</div>
|
||||
<br/>
|
||||
@ -427,38 +462,43 @@ class Step2 extends Component {
|
||||
<p className="applicationCreateIconTitle">Icon*:</p>
|
||||
<GridList className="applicationCreateIconGrid" cols={1.1}>
|
||||
{this.state.icon.map((tile) => (
|
||||
<GridTile key={Math.floor(Math.random() * 1000)}
|
||||
title={tile.name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this._removeIcon.bind(this)}>
|
||||
<Clear />
|
||||
</IconButton>}>
|
||||
<img src={tile.preview}/></GridTile>
|
||||
<GridTile
|
||||
key={Math.floor(Math.random() * 1000)}
|
||||
title={tile.name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this.removeIcon.bind(this)}>
|
||||
<Clear/>
|
||||
</IconButton>}>
|
||||
<img src={tile.preview}/>
|
||||
</GridTile>
|
||||
))}
|
||||
{this.state.icon.length === 0 ?
|
||||
<Dropzone className="applicationCreateIconDropZone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(icon, rejected) => {this.setState({icon, rejected});}}>
|
||||
<p className="applicationCreateIconp">+</p>
|
||||
</Dropzone> : <div />}
|
||||
<Dropzone
|
||||
className="applicationCreateIconDropZone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(icon, rejected) => {
|
||||
this.setState({icon, rejected});
|
||||
}}
|
||||
>
|
||||
<p className="applicationCreateIconp">+</p>
|
||||
</Dropzone> : <div/>}
|
||||
</GridList>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<div className="applicationCreateBackAndNext">
|
||||
<FlatButton
|
||||
label="< Back"
|
||||
disabled={false}
|
||||
onClick={this._handlePrev.bind(this)}
|
||||
className="applicationCreateBack"
|
||||
onClick={this.handlePrev.bind(this)}
|
||||
style={{marginRight: 12}}
|
||||
/>
|
||||
<RaisedButton
|
||||
label="Next >"
|
||||
primary={true}
|
||||
onClick={this._handleNext.bind(this)}
|
||||
onClick={this.handleNext.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -63,7 +63,7 @@ class Step3 extends Component {
|
||||
/**
|
||||
*Loading the theme files based on the the user-preference.
|
||||
*/
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -74,21 +74,21 @@ class Step3 extends Component {
|
||||
* Handles finish button click.
|
||||
* This invokes handleNext function in parent component.
|
||||
* */
|
||||
_handleFinish() {
|
||||
handleFinish() {
|
||||
this.props.handleFinish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes Prev button click.
|
||||
* */
|
||||
_handlePrev() {
|
||||
handlePrev() {
|
||||
this.props.handlePrev();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles release application selection.
|
||||
* */
|
||||
_handleToggle() {
|
||||
handleToggle() {
|
||||
let hide = this.state.showForm;
|
||||
this.setState({showForm: !hide});
|
||||
}
|
||||
@ -100,36 +100,42 @@ class Step3 extends Component {
|
||||
<Toggle
|
||||
label="Release the Application"
|
||||
labelPosition="right"
|
||||
onToggle={this._handleToggle.bind(this)}
|
||||
onToggle={this.handleToggle.bind(this)}
|
||||
defaultToggled={this.state.showForm}
|
||||
/>
|
||||
{/*If toggle is true, the release form will be shown.*/}
|
||||
{!this.state.showForm ? <div/> : <div>
|
||||
<SelectField
|
||||
floatingLabelText="Select Release Channel*"
|
||||
value={this.state.releaseChannel}
|
||||
floatingLabelFixed={true}
|
||||
>
|
||||
<MenuItem value={1} primaryText="Alpha"/>
|
||||
<MenuItem value={2} primaryText="Beta"/>
|
||||
<MenuItem value={3} primaryText="GA"/>
|
||||
</SelectField> <br/>
|
||||
<TextField
|
||||
hintText="1.0.0"
|
||||
floatingLabelText="Version*"
|
||||
errorText={this.state.errors["title"]}
|
||||
floatingLabelFixed={true}
|
||||
/><br/>
|
||||
</div>}
|
||||
|
||||
{!this.state.showForm ? <div/> :
|
||||
<div>
|
||||
<SelectField
|
||||
floatingLabelText="Select Release Channel*"
|
||||
value={this.state.releaseChannel}
|
||||
floatingLabelFixed={true}
|
||||
>
|
||||
<MenuItem value={1} primaryText="Alpha"/>
|
||||
<MenuItem value={2} primaryText="Beta"/>
|
||||
<MenuItem value={3} primaryText="GA"/>
|
||||
</SelectField>
|
||||
<br/>
|
||||
<TextField
|
||||
hintText="1.0.0"
|
||||
floatingLabelText="Version*"
|
||||
errorText={this.state.errors["title"]}
|
||||
floatingLabelFixed={true}
|
||||
/><br/>
|
||||
</div>}
|
||||
<div className="applicationCreateBackAndFinish">
|
||||
<FlatButton label="< Back" disabled={false} onClick={this._handlePrev.bind(this)}
|
||||
className="applicationCreateFinish"/>
|
||||
<FlatButton
|
||||
label="< Back"
|
||||
disabled={false}
|
||||
onClick={this.handlePrev.bind(this)}
|
||||
className="applicationCreateFinish"/>
|
||||
|
||||
<RaisedButton
|
||||
label="Finish"
|
||||
primary={true}
|
||||
onClick={this._handleFinish.bind(this)}
|
||||
onClick={this.handleFinish.bind(this)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -97,67 +97,74 @@ class BaseLayout extends Component {
|
||||
return (
|
||||
|
||||
<div>
|
||||
<AppBar title="App Publisher"
|
||||
iconElementRight={
|
||||
<div>
|
||||
<Badge
|
||||
badgeContent={this.state.notifications}
|
||||
secondary={true}
|
||||
badgeStyle={{top: 12, right: 12}}
|
||||
>
|
||||
<IconButton tooltip="Notifications">
|
||||
<NotificationsIcon/>
|
||||
</IconButton>
|
||||
</Badge>
|
||||
<IconButton onClick={() => {
|
||||
console.log("Clicked")
|
||||
}}>
|
||||
<ActionAccountCircle/>
|
||||
<AppBar
|
||||
title="App Publisher"
|
||||
iconElementRight={
|
||||
<div>
|
||||
<Badge
|
||||
badgeContent={this.state.notifications}
|
||||
secondary={true}
|
||||
badgeStyle={{top: 12, right: 12}}
|
||||
>
|
||||
<IconButton tooltip="Notifications">
|
||||
<NotificationsIcon/>
|
||||
</IconButton>
|
||||
</div>
|
||||
}
|
||||
</Badge>
|
||||
<IconButton onClick={() => {
|
||||
console.log("Clicked")
|
||||
}}>
|
||||
<ActionAccountCircle/>
|
||||
</IconButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Drawer containerStyle={{height: 'calc(100% - 64px)', width: '15%', top: '10%'}} open={true}>
|
||||
<List>
|
||||
<ListItem primaryText="Applications"
|
||||
leftIcon={<Apps/>}
|
||||
initiallyOpen={false}
|
||||
primaryTogglesNestedList={true}
|
||||
onClick={this.handleApplicationClick.bind(this)}
|
||||
nestedItems={[
|
||||
<ListItem
|
||||
key={1}
|
||||
primaryText="Create"
|
||||
onClick={this.handleApplicationCreateClick.bind(this)}
|
||||
leftIcon={<Add/>}
|
||||
/>]}
|
||||
<ListItem
|
||||
primaryText="Applications"
|
||||
leftIcon={<Apps/>}
|
||||
initiallyOpen={false}
|
||||
primaryTogglesNestedList={true}
|
||||
onClick={this.handleApplicationClick.bind(this)}
|
||||
nestedItems={[
|
||||
<ListItem
|
||||
key={1}
|
||||
primaryText="Create"
|
||||
onClick={this.handleApplicationCreateClick.bind(this)}
|
||||
leftIcon={<Add/>}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
<ListItem primaryText="Platforms"
|
||||
leftIcon={<DevicesOther/>}
|
||||
initiallyOpen={false}
|
||||
primaryTogglesNestedList={true}
|
||||
onClick={this.handlePlatformClick.bind(this)}
|
||||
nestedItems={[
|
||||
<ListItem
|
||||
key={1}
|
||||
primaryText="Create"
|
||||
onClick={this.handlePlatformCreateClick.bind(this)}
|
||||
leftIcon={<Add/>}
|
||||
/>]}
|
||||
<ListItem
|
||||
primaryText="Platforms"
|
||||
leftIcon={<DevicesOther/>}
|
||||
initiallyOpen={false}
|
||||
primaryTogglesNestedList={true}
|
||||
onClick={this.handlePlatformClick.bind(this)}
|
||||
nestedItems={[
|
||||
<ListItem
|
||||
key={1}
|
||||
primaryText="Create"
|
||||
onClick={this.handlePlatformCreateClick.bind(this)}
|
||||
leftIcon={<Add/>}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
<ListItem
|
||||
primaryText="Reviews"
|
||||
onClick={this.handleReviewClick.bind(this)}
|
||||
leftIcon={<Feedback/>}
|
||||
/>
|
||||
<ListItem primaryText="Reviews"
|
||||
onClick={this.handleReviewClick.bind(this)}
|
||||
leftIcon={<Feedback/>}/>
|
||||
</List>
|
||||
</Drawer>
|
||||
</div>
|
||||
<div className="basicLayoutDiv">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BaseLayout.propTypes = {
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import Axios from 'axios';
|
||||
import Chip from 'material-ui/Chip';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import React, {Component} from 'react';
|
||||
@ -34,6 +33,7 @@ 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 Theme from '../../theme';
|
||||
import Endpoint from '../../api/endpoints';
|
||||
|
||||
/**
|
||||
* Platform Create component.
|
||||
@ -61,6 +61,7 @@ class PlatformCreate extends Component {
|
||||
description: "",
|
||||
property: "",
|
||||
icon: [],
|
||||
identifier: "",
|
||||
propertyTypes: [
|
||||
{key: 0, value: 'String'},
|
||||
{key: 1, value: 'Number'},
|
||||
@ -74,7 +75,7 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
*Loading the theme files based on the the user-preference.
|
||||
*/
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -85,7 +86,7 @@ class PlatformCreate extends Component {
|
||||
* Handles toggle button actions.
|
||||
* One method is used for all the toggle buttons and, each toggle is identified by the id.
|
||||
* */
|
||||
_handleToggle(event) {
|
||||
handleToggle(event) {
|
||||
switch (event.target.id) {
|
||||
case "enabled" : {
|
||||
let enabled = this.state.enabled;
|
||||
@ -103,28 +104,28 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Triggers the onChange action on property type selection.
|
||||
* */
|
||||
_onPropertySelect = (event, index, value) => {
|
||||
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) => {
|
||||
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) {
|
||||
addTags(event) {
|
||||
let tags = this.state.tags;
|
||||
if (event.charCode === 13) {
|
||||
event.preventDefault();
|
||||
@ -136,11 +137,11 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Creates Chip array from state.tags.
|
||||
* */
|
||||
_renderChip(data) {
|
||||
renderChip(data) {
|
||||
return (
|
||||
<Chip
|
||||
key={data.key}
|
||||
onRequestDelete={() => this._handleTagDelete(data.key)}
|
||||
onRequestDelete={() => this.handleTagDelete(data.key)}
|
||||
style={this.styles.chip}
|
||||
>
|
||||
{data.value}
|
||||
@ -151,7 +152,7 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Set the value for tag.
|
||||
* */
|
||||
_handleTagChange(event) {
|
||||
handleTagChange(event) {
|
||||
let defaultValue = this.state.defValue;
|
||||
defaultValue = event.target.value;
|
||||
this.setState({defValue: defaultValue})
|
||||
@ -160,7 +161,7 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Remove the selected property from the property list.
|
||||
* */
|
||||
_removeProperty(property) {
|
||||
removeProperty(property) {
|
||||
let properties = this.state.platformProperties;
|
||||
properties.splice(properties.indexOf(property), 1);
|
||||
this.setState({platformProperties: properties});
|
||||
@ -169,28 +170,31 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Add a new platform property.
|
||||
* */
|
||||
_addProperty() {
|
||||
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
|
||||
}]),
|
||||
this.setState({
|
||||
platformProperties:
|
||||
this.state.platformProperties.concat([
|
||||
{
|
||||
key: property,
|
||||
value: this.state.propertyTypes[selected].value
|
||||
}]),
|
||||
property: "",
|
||||
selectedProperty: 0});
|
||||
selectedProperty: 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
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": {
|
||||
@ -210,13 +214,26 @@ class PlatformCreate extends Component {
|
||||
this.setState({property: property});
|
||||
break;
|
||||
}
|
||||
case "identifier": {
|
||||
identifier = value;
|
||||
this.setState({identifier: identifier});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onCreatePlatform() {
|
||||
onCreatePlatform() {
|
||||
//Call the platform create api.
|
||||
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;
|
||||
|
||||
Endpoint.createPlatform(platform);
|
||||
|
||||
|
||||
}
|
||||
@ -224,22 +241,24 @@ class PlatformCreate extends Component {
|
||||
/**
|
||||
* Remove the uploaded icon.
|
||||
* */
|
||||
_removeIcon(event) {
|
||||
removeIcon(event) {
|
||||
this.setState({icon: []});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the user entered values in the form.
|
||||
* */
|
||||
_clearForm() {
|
||||
this.setState({enabled: true,
|
||||
clearForm() {
|
||||
this.setState({
|
||||
enabled: true,
|
||||
allTenants: false,
|
||||
files: [],
|
||||
platformProperties: [],
|
||||
selectedProperty: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
property: "",})
|
||||
property: "",
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -253,24 +272,35 @@ class PlatformCreate extends Component {
|
||||
tags,
|
||||
defValue,
|
||||
description,
|
||||
property} = this.state;
|
||||
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.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
hintText="Enter the Platform Name."
|
||||
id="name"
|
||||
floatingLabelText="Name*"
|
||||
floatingLabelFixed={true}
|
||||
value={name}
|
||||
onChange={this._onTextChange.bind(this)}
|
||||
/><br/>
|
||||
onChange={this.onTextChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
id="description"
|
||||
hintText="Enter the Platform Description."
|
||||
@ -279,33 +309,38 @@ class PlatformCreate extends Component {
|
||||
multiLine={true}
|
||||
rows={2}
|
||||
value={description}
|
||||
onChange={this._onTextChange.bind(this)}
|
||||
/><br/><br/>
|
||||
onChange={this.onTextChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<br/>
|
||||
<Toggle
|
||||
id="tenant"
|
||||
label="Shared with all Tenants"
|
||||
labelPosition="right"
|
||||
onToggle={this._handleToggle.bind(this)}
|
||||
onToggle={this.handleToggle.bind(this)}
|
||||
toggled={allTenants}
|
||||
/> <br/>
|
||||
/>
|
||||
<br/>
|
||||
<Toggle
|
||||
id="enabled"
|
||||
label="Enabled"
|
||||
labelPosition="right"
|
||||
onToggle={this._handleToggle.bind(this)}
|
||||
onToggle={this.handleToggle.bind(this)}
|
||||
toggled={enabled}
|
||||
/> <br/>
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
id="tags"
|
||||
hintText="Enter Platform tags.."
|
||||
floatingLabelText="Tags*"
|
||||
floatingLabelFixed={true}
|
||||
value={defValue}
|
||||
onChange={this._handleTagChange.bind(this)}
|
||||
onKeyPress={this._addTags.bind(this)}
|
||||
/><br/>
|
||||
onChange={this.handleTagChange.bind(this)}
|
||||
onKeyPress={this.addTags.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<div style={this.styles.wrapper}>
|
||||
{tags.map(this._renderChip, this)}
|
||||
{tags.map(this.renderChip, this)}
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
@ -313,7 +348,7 @@ class PlatformCreate extends Component {
|
||||
<div id="property-container">
|
||||
{platformProperties.map((p) => {
|
||||
return <div key={p.key}>{p.key} : {p.value}
|
||||
<IconButton onClick={this._removeProperty.bind(this, p)}>
|
||||
<IconButton onClick={this.removeProperty.bind(this, p)}>
|
||||
<Close className="createplatformpropertyclose"/>
|
||||
</IconButton>
|
||||
</div>
|
||||
@ -326,21 +361,21 @@ class PlatformCreate extends Component {
|
||||
floatingLabelText="Platform Property*"
|
||||
floatingLabelFixed={true}
|
||||
value={this.state.property}
|
||||
onChange={this._onTextChange.bind(this)}
|
||||
onChange={this.onTextChange.bind(this)}
|
||||
/> <em/>
|
||||
<SelectField
|
||||
className="createplatformpropertyselect"
|
||||
floatingLabelText="Property Type"
|
||||
value={selectedProperty}
|
||||
floatingLabelFixed={true}
|
||||
onChange={this._onPropertySelect.bind(this)}>
|
||||
onChange={this.onPropertySelect.bind(this)}>
|
||||
{propertyTypes.map((type) => {
|
||||
return <MenuItem key={type.key}
|
||||
value={type.key}
|
||||
primaryText={type.value}/>
|
||||
return <MenuItem key={type.key}
|
||||
value={type.key}
|
||||
primaryText={type.value}/>
|
||||
})}
|
||||
</SelectField>
|
||||
<IconButton onClick={this._addProperty.bind(this)}>
|
||||
<IconButton onClick={this.addProperty.bind(this)}>
|
||||
<AddCircleOutline/>
|
||||
</IconButton>
|
||||
<br/>
|
||||
@ -350,27 +385,33 @@ class PlatformCreate extends Component {
|
||||
<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.bind(this)}>
|
||||
<Clear />
|
||||
</IconButton>}>
|
||||
<GridTile
|
||||
key={Math.floor(Math.random() * 1000)}
|
||||
title={tile.name}
|
||||
actionIcon={
|
||||
<IconButton onClick={this.removeIcon.bind(this)}>
|
||||
<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})}}>
|
||||
<Dropzone
|
||||
className="createplatformdropzone"
|
||||
accept="image/jpeg, image/png"
|
||||
onDrop={(icon, rejected) => {
|
||||
this.setState({icon, rejected})
|
||||
}}
|
||||
>
|
||||
<p className="createplatformdropzonep">+</p>
|
||||
</Dropzone> : <div />}
|
||||
</Dropzone> : <div/>}
|
||||
</GridList>
|
||||
</div>
|
||||
<br/>
|
||||
<RaisedButton primary={true} label="Create"
|
||||
onClick={this._onCreatePlatform.bind(this)}/>
|
||||
<FlatButton label="Cancel" onClick={this._clearForm.bind(this)}/>
|
||||
<RaisedButton
|
||||
primary={true} label="Create"
|
||||
onClick={this.onCreatePlatform.bind(this)}/>
|
||||
<FlatButton label="Cancel" onClick={this.clearForm.bind(this)}/>
|
||||
</form>
|
||||
</div>
|
||||
</CardActions>
|
||||
@ -380,7 +421,6 @@ class PlatformCreate extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
PlatformCreate.prototypes = {
|
||||
};
|
||||
PlatformCreate.prototypes = {};
|
||||
|
||||
export default PlatformCreate;
|
||||
|
||||
@ -56,7 +56,7 @@ class PlatformListing extends Component {
|
||||
* Handles the search action.
|
||||
* When typing in the search bar, this method will be invoked.
|
||||
* */
|
||||
_searchApplications(word) {
|
||||
searchApplications(word) {
|
||||
let searchedData = [];
|
||||
}
|
||||
|
||||
@ -64,13 +64,13 @@ class PlatformListing extends Component {
|
||||
* Handles sort data function and toggles the asc state.
|
||||
* asc: true : sort in ascending order.
|
||||
* */
|
||||
_sortData() {
|
||||
sortData() {
|
||||
let isAsc = this.state.asc;
|
||||
let datas = isAsc?this.data.sort(this._compare):this.data.reverse();
|
||||
let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse();
|
||||
this.setState({data: datas, asc: !isAsc});
|
||||
}
|
||||
|
||||
_compare(a, b) {
|
||||
compare(a, b) {
|
||||
if (a.applicationName < b.applicationName)
|
||||
return -1;
|
||||
if (a.applicationName > b.applicationName)
|
||||
@ -78,24 +78,25 @@ class PlatformListing extends Component {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_onRowClick(id) {
|
||||
onRowClick(id) {
|
||||
console.log(id)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className= 'middle listingplatformmiddle'>
|
||||
<div className='middle listingplatformmiddle'>
|
||||
<Card className='listingplatformcard'>
|
||||
<TextField hintText="Search" onChange={this._searchApplications.bind(this)}
|
||||
<TextField hintText="Search" onChange={this.searchApplications.bind(this)}
|
||||
className='listingplatformsearch'/>
|
||||
<CardTitle title="Platforms" className='listingplatformTitle'/>
|
||||
<CardActions>
|
||||
|
||||
</CardActions>
|
||||
<DataTable headers={this.headers}
|
||||
data={this.data}
|
||||
handleRowClick={this._onRowClick.bind(this)}
|
||||
noDataMessage={{type: 'button', text: 'Create Platform'}}/>
|
||||
<DataTable
|
||||
headers={this.headers}
|
||||
data={this.data}
|
||||
handleRowClick={this.onRowClick.bind(this)}
|
||||
noDataMessage={{type: 'button', text: 'Create Platform'}}/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -83,7 +83,7 @@ class DataTable extends Component {
|
||||
* Triggers when user click on table row.
|
||||
* This method invokes the parent method handleRowClick, which is passed via props.
|
||||
* */
|
||||
_handleRowClick(id) {
|
||||
handleRowClick(id) {
|
||||
this.props.handleRowClick(id);
|
||||
}
|
||||
|
||||
@ -100,22 +100,29 @@ class DataTable extends Component {
|
||||
|
||||
if (data) {
|
||||
return (<Table
|
||||
selectable={ false }>
|
||||
<TableHeader displaySelectAll={ false }
|
||||
adjustForCheckbox={ false }>
|
||||
selectable={false}>
|
||||
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
|
||||
<TableRow>
|
||||
{headers.map((header) => {
|
||||
return (<DataTableHeader key={header.data_id} className="datatableRowColumn"
|
||||
header={header}/>)
|
||||
}
|
||||
return (
|
||||
<DataTableHeader
|
||||
key={header.data_id}
|
||||
className="datatableRowColumn"
|
||||
header={header}
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((dataItem) =>{
|
||||
return (<DataTableRow key={dataItem.id}
|
||||
dataItem={dataItem}
|
||||
handleClick={this._handleRowClick.bind(this)}/>)
|
||||
{data.map((dataItem) => {
|
||||
return (
|
||||
<DataTableRow
|
||||
key={dataItem.id}
|
||||
dataItem={dataItem}
|
||||
handleClick={this.handleRowClick.bind(this)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>)
|
||||
|
||||
@ -48,7 +48,7 @@ class DataTableHeader extends Component {
|
||||
* The onClick function of the table header.
|
||||
* Invokes the function passed in the header object.
|
||||
* */
|
||||
_tableHeaderClick() {
|
||||
tableHeaderClick() {
|
||||
this.props.header.sort();
|
||||
}
|
||||
|
||||
@ -60,14 +60,18 @@ class DataTableHeader extends Component {
|
||||
* else create a span element with label as the table header.
|
||||
* */
|
||||
if (this.props.header.sortable) {
|
||||
headerCell = <FlatButton label={this.props.header.label}
|
||||
onClick={this._tableHeaderClick.bind(this)} className="sortableHeaderCell"/>;
|
||||
headerCell =
|
||||
<FlatButton
|
||||
label={this.props.header.label}
|
||||
onClick={this.tableHeaderClick.bind(this)}
|
||||
className="sortableHeaderCell"
|
||||
/>;
|
||||
} else {
|
||||
headerCell = <span className="notsortableHeaderCell">{this.props.header.label}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHeaderColumn key={this.props.header.id} className="datatableHeaderColumn" >
|
||||
<TableHeaderColumn key={this.props.header.id} className="datatableHeaderColumn">
|
||||
{headerCell}
|
||||
</TableHeaderColumn>
|
||||
);
|
||||
|
||||
@ -41,7 +41,7 @@ class DataTableRow extends Component {
|
||||
/**
|
||||
*Loading the theme files based on the the user-preference.
|
||||
*/
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
Theme.insertThemingScripts(this.scriptId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -51,24 +51,32 @@ class DataTableRow extends Component {
|
||||
/**
|
||||
* Triggers the click event on the data table row.
|
||||
* */
|
||||
_handleClick() {
|
||||
handleClick() {
|
||||
this.props.handleClick(this.state.dataItem.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {dataItem} = this.state;
|
||||
return (
|
||||
<TableRow key={this.props.key} onClick={this._handleClick.bind(this)} >
|
||||
{Object.keys(dataItem).map((key) => {
|
||||
if (key !== 'id') {
|
||||
return <TableRowColumn className = "datatableRowColumn"
|
||||
key={key}>{dataItem[key]}</TableRowColumn>
|
||||
} else {
|
||||
return <TableRowColumn key={key}/>
|
||||
}
|
||||
<TableRow
|
||||
key={this.props.key}
|
||||
onClick={this.handleClick.bind(this)}
|
||||
>
|
||||
{Object.keys(dataItem).map((key) => {
|
||||
if (key !== 'id') {
|
||||
return (
|
||||
<TableRowColumn
|
||||
className="datatableRowColumn"
|
||||
key={key}
|
||||
>
|
||||
{dataItem[key]}
|
||||
</TableRowColumn>)
|
||||
} else {
|
||||
return <TableRowColumn key={key}/>
|
||||
}
|
||||
|
||||
} )}
|
||||
</TableRow>
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import React, {Component} from 'react';
|
||||
import Checkbox from 'material-ui/Checkbox';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import {Redirect, Switch} from 'react-router-dom';
|
||||
import AuthHandler from '../../../api/authHandler';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
import {Card, CardActions, CardTitle} from 'material-ui/Card';
|
||||
|
||||
@ -65,15 +66,15 @@ class Login extends Component {
|
||||
// }
|
||||
}
|
||||
|
||||
_handleLogin(event) {
|
||||
handleLogin(event) {
|
||||
event.preventDefault();
|
||||
this._validateForm();
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the username field change event.
|
||||
* */
|
||||
_onUserNameChange(event, value) {
|
||||
onUserNameChange(event, value) {
|
||||
this.setState(
|
||||
{
|
||||
userName: value
|
||||
@ -84,7 +85,7 @@ class Login extends Component {
|
||||
/**
|
||||
* Handles the password field change event.
|
||||
* */
|
||||
_onPasswordChange(event, value) {
|
||||
onPasswordChange(event, value) {
|
||||
this.setState(
|
||||
{
|
||||
password: value
|
||||
@ -95,7 +96,7 @@ class Login extends Component {
|
||||
/**
|
||||
* Handles the remember me check.
|
||||
* */
|
||||
_handleRememberMe() {
|
||||
handleRememberMe() {
|
||||
this.setState(
|
||||
{
|
||||
rememberMe: !this.state.rememberMe
|
||||
@ -106,17 +107,32 @@ class Login extends Component {
|
||||
/**
|
||||
* Validate the login form.
|
||||
* */
|
||||
_validateForm() {
|
||||
validateForm() {
|
||||
let errors = {};
|
||||
let validationFailed = true;
|
||||
if (!this.state.password) {
|
||||
errors["passwordError"] = "Password is Required";
|
||||
validationFailed = true;
|
||||
} else {
|
||||
validationFailed = false;
|
||||
}
|
||||
|
||||
if (!this.state.userName) {
|
||||
errors["userNameError"] = "User Name is Required";
|
||||
validationFailed = true;
|
||||
} else {
|
||||
validationFailed = false;
|
||||
}
|
||||
|
||||
this.setState({errors: errors}, console.log(errors));
|
||||
if (validationFailed) {
|
||||
this.setState({errors: errors}, console.log(errors));
|
||||
} else {
|
||||
let loginPromis = AuthHandler.login(this.state.userName, this.state.password);
|
||||
loginPromis.then(response => {
|
||||
console.log(AuthHandler.getUser());
|
||||
this.setState({isLoggedIn: AuthHandler.getUser()});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -124,13 +140,12 @@ class Login extends Component {
|
||||
if (!this.state.isLoggedIn) {
|
||||
return (
|
||||
<div>
|
||||
|
||||
{/*TODO: Style the components.*/}
|
||||
|
||||
<Card>
|
||||
<CardTitle title="WSO2 IoT App Publisher"/>
|
||||
<CardActions>
|
||||
<form onSubmit={this._handleLogin.bind(this)}>
|
||||
<form onSubmit={this.handleLogin.bind(this)}>
|
||||
<TextField
|
||||
hintText="Enter the User Name."
|
||||
id="username"
|
||||
@ -138,8 +153,9 @@ class Login extends Component {
|
||||
floatingLabelText="User Name*"
|
||||
floatingLabelFixed={true}
|
||||
value={this.state.userName}
|
||||
onChange={this._onUserNameChange.bind(this)}
|
||||
/><br/>
|
||||
onChange={this.onUserNameChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<TextField
|
||||
hintText="Enter the Password."
|
||||
id="password"
|
||||
@ -148,11 +164,14 @@ class Login extends Component {
|
||||
floatingLabelText="Password*"
|
||||
floatingLabelFixed={true}
|
||||
value={this.state.password}
|
||||
onChange={this._onPasswordChange.bind(this)}
|
||||
/><br/>
|
||||
<Checkbox label="Remember me."
|
||||
onCheck={this._handleRememberMe.bind(this)}
|
||||
checked={this.state.rememberMe}/>
|
||||
onChange={this.onPasswordChange.bind(this)}
|
||||
/>
|
||||
<br/>
|
||||
<Checkbox
|
||||
label="Remember me."
|
||||
onCheck={this.handleRememberMe.bind(this)}
|
||||
checked={this.state.rememberMe}
|
||||
/>
|
||||
<br/>
|
||||
<RaisedButton type="submit" label="Login"/>
|
||||
</form>
|
||||
|
||||
@ -21,7 +21,6 @@ import NotFound from './Error/NotFound';
|
||||
import BaseLayout from './Base/BaseLayout';
|
||||
import PlatformCreate from './Platform/PlatformCreate';
|
||||
import PlatformListing from './Platform/PlatformListing';
|
||||
import PublisherOverview from './Overview/PublisherOverview';
|
||||
import ApplicationCreate from './Application/ApplicationCreate';
|
||||
import ApplicationListing from './Application/ApplicationListing';
|
||||
|
||||
@ -29,5 +28,4 @@ import ApplicationListing from './Application/ApplicationListing';
|
||||
* Contains all UI components related to Application, Login and Platform
|
||||
*/
|
||||
|
||||
export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PublisherOverview,
|
||||
PlatformCreate};
|
||||
export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PlatformCreate};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user