Commit f1bef87c authored by Patricio Bruna's avatar Patricio Bruna

Merge branch 'master' of github.com:ZBoxApp/manager-react

parents 27a7a663 fe727adf
...@@ -27,7 +27,7 @@ The is a configuration file under `config/config.json` with the following struct ...@@ -27,7 +27,7 @@ The is a configuration file under `config/config.json` with the following struct
* **debug**: Use in development environmet to recieve messages in the javascript console. * **debug**: Use in development environmet to recieve messages in the javascript console.
* **zimbraUrl**: The URL where the zimbra admin services are running. * **zimbraUrl**: The URL where the zimbra admin services are running.
* **zimbraProxy**: The URL of a proxy server to allow CORS between the webapp and the Zimbra admin service. * **zimbraProxy**: The URL of a proxy server to allow CORS between the webapp and the Zimbra admin service. (Only needed in **development** environment for the server)
* **dnsApiUrl**: URL of the DNS api to get DNS information ( [zDNS](https://github.com/ZBoxApp/zDNS) ). * **dnsApiUrl**: URL of the DNS api to get DNS information ( [zDNS](https://github.com/ZBoxApp/zDNS) ).
* **plans**: Object with the available mailboxes plans, set to *true* if enabled or *false* to disable it. * **plans**: Object with the available mailboxes plans, set to *true* if enabled or *false* to disable it.
* **companiesEndPoints**: This are the enpoints to get information about companies and invoices. (Checkout the companies endpoint specifications below). * **companiesEndPoints**: This are the enpoints to get information about companies and invoices. (Checkout the companies endpoint specifications below).
...@@ -99,6 +99,20 @@ Posible status are: ...@@ -99,6 +99,20 @@ Posible status are:
| 2 | Vencida | | 2 | Vencida |
| 3 | Anulada | | 3 | Anulada |
## Deployment
You'll need to build the sources ready for production by running
```
$ make build
```
This will generate a folder called `dist` which holds all the necessary files for deploying the website.
This files have to be copied to your server running `NGINX`.
Copy the files to `NGINX` html folder and you're done.
A sample for the `NGINX` configuration can be found [here](nginx/nginx.conf).
## TODO ## TODO
* Add security to the companies Endpoints * Add security to the companies Endpoints
#user nobody;
#Defines which Linux system user will own and run the Nginx server
worker_processes 1;
#Referes to single threaded process. Generally set to be equal to the number of CPUs or cores.
#error_log logs/error.log; #error_log logs/error.log notice;
#Specifies the file where server logs.
#pid logs/nginx.pid;
#nginx will write its master process ID(PID).
events {
worker_connections 1024;
# worker_processes and worker_connections allows you to calculate maxclients value:
# max_clients = worker_processes * worker_connections
}
http {
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile off;
# If serving locally stored static files, sendfile is essential to speed up the server,
# But if using as reverse proxy one can deactivate it
#tcp_nopush on;
# works opposite to tcp_nodelay. Instead of optimizing delays, it optimizes the amount of data sent at once.
#keepalive_timeout 0;
keepalive_timeout 65;
# timeout during which a keep-alive client connection will stay open.
#gzip on;
# tells the server to use on-the-fly gzip compression.
server {
# You would want to make a separate file with its own server block for each virtual domain
# on your server and then include them.
listen 80;
#tells Nginx the hostname and the TCP port where it should listen for HTTP connections.
# listen 80; is equivalent to listen *:80;
server_name localhost;
# lets you doname-based virtual hosting
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#The location setting lets you configure how nginx responds to requests for resources within the server.
root /html;
index index.html index.htm;
try_files $uri /index.html;
}
#error_page 404 /404.html;
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
server {
listen 8001;
# server_name somename alias another.alias;
location /service/admin/soap {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass https://192.168.50.10:7071/service/admin/soap;
}
}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
...@@ -20,11 +20,12 @@ ...@@ -20,11 +20,12 @@
"react-bootstrap-datetimepicker": "0.0.22", "react-bootstrap-datetimepicker": "0.0.22",
"react-datalist": "^3.0.0", "react-datalist": "^3.0.0",
"react-dom": "15.0.1", "react-dom": "15.0.1",
"react-password-strength-meter": "^1.0.5",
"react-router": "2.3.0", "react-router": "2.3.0",
"react-textarea-autosize": "4.0.0", "react-textarea-autosize": "4.0.0",
"react-toastr": "^2.6.0", "react-toastr": "^2.6.0",
"toastr": "^2.1.2", "toastr": "^2.1.2",
"zimbra-admin-api-js": "ZBoxApp/zimbra-admin-api-js#master" "zimbra-admin-api-js": "ZBoxApp/zimbra-admin-api-js"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "^6.7.5", "babel-cli": "^6.7.5",
...@@ -62,7 +63,7 @@ ...@@ -62,7 +63,7 @@
}, },
"scripts": { "scripts": {
"check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .", "check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .",
"build": "webpack", "build": "NODE_ENV=production webpack",
"run": "webpack --progress --watch", "run": "webpack --progress --watch",
"run-fullmap": "webpack --progress --watch", "run-fullmap": "webpack --progress --watch",
"companies-service": "babel-node companies-service.js", "companies-service": "babel-node companies-service.js",
......
...@@ -11,12 +11,26 @@ export function emitStartLoading() { ...@@ -11,12 +11,26 @@ export function emitStartLoading() {
}); });
} }
export function emitStartTask(params) {
AppDispatcher.handleViewAction({
type: ActionTypes.START_TASK_LOADING,
params
});
}
export function emitEndLoading() { export function emitEndLoading() {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.END_LOADING type: ActionTypes.END_LOADING
}); });
} }
export function emitEndTask(params) {
AppDispatcher.handleViewAction({
type: ActionTypes.END_TASK_LOADING,
params
});
}
export function emitMessage(attrs) { export function emitMessage(attrs) {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.NEW_MESSAGE, type: ActionTypes.NEW_MESSAGE,
......
This diff is collapsed.
import $ from 'jquery';
import React from 'react';
import Button from '../button.jsx';
import MessageBar from '../message_bar.jsx';
import Panel from '../panel.jsx';
import * as Client from '../../utils/client.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import * as Utils from '../../utils/utils.jsx';
import EventStore from '../../stores/event_store.jsx';
import Promise from 'bluebird';
import Constants from '../../utils/constants.jsx';
const messageType = Constants.MessageType;
export default class EditDistributionList extends React.Component {
constructor(props) {
super(props);
this.showMessage = this.showMessage.bind(this);
this.saveChanges = this.saveChanges.bind(this);
this.state = {};
}
showMessage(attrs) {
this.setState({
error: attrs.error,
type: attrs.typeError
});
}
saveChanges(e) {
e.preventDefault();
Utils.toggleStatusButtons('.action-edit-dl', true);
Utils.validateInputRequired(this.refs).then(() => {
const id = this.props.params.id;
const attrs = {};
const mail = this.refs.mail.value;
attrs.displayName = this.refs.displayName.value;
Client.modifyDistributionList(id, attrs, (dl) => {
const domain = dl.name.split('@').pop();
const newNameDL = `${mail}@${domain}`;
dl.rename(newNameDL, (err) => {
Utils.toggleStatusButtons('.action-edit-dl', false);
if (err) {
return GlobalActions.emitMessage({
error: err.extra.reason,
typeError: messageType.ERROR
});
}
return GlobalActions.emitMessage({
error: 'Se han actualizado sus datos éxitosamente.',
typeError: messageType.SUCCESS
});
});
}, (err) => {
GlobalActions.emitMessage({
error: err.message,
typeError: messageType.ERROR
});
Utils.toggleStatusButtons('.action-edit-dl', false);
});
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
type: messageType.ERROR
});
error.node.focus();
Utils.toggleStatusButtons('.action-edit-dl', false);
});
}
getDistributionList() {
const id = this.props.params.id;
new Promise((resolve, reject) => {
Client.getDistributionList(id, (success) => {
resolve(success);
}, (error) => {
reject(error);
});
}).then((res) => {
const domain = this.refs.domain;
const displayName = this.refs.displayName;
const mail = this.refs.mail;
const dl = res.name.split('@');
domain.innerHTML = dl.pop();
if (res.attrs.displayName) {
displayName.value = res.attrs.displayName;
}
mail.value = dl.shift();
}).catch((err) => {
GlobalActions.emitMessage({
error: err.message,
type: messageType.ERROR
});
Utils.toggleStatusButtons('.action-edit-dl', true);
}).finally(() => {
GlobalActions.emitEndLoading();
});
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
GlobalActions.emitEndLoading();
$('#sidebar-domain').removeClass('active');
this.getDistributionList();
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
$('#sidebar-domain').removeClass('active');
}
render() {
let message;
let form;
let actions;
if (this.state.error) {
message = (
<MessageBar
message={this.state.error}
type={this.state.type}
autoclose={true}
/>
);
}
form = (
<form
className='simple_form form-horizontal mailbox-form'
id='editAccount'
>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
<abbr title='requerido'>{'*'}</abbr>
{'Dirección'}
</label>
<div className='col-sm-8'>
<div className='input-group'>
<input
type='text'
className='form-control'
ref='mail'
data-required='true'
data-message='La dirección de correo es requerida, por favor verifique.'
placeholder='Dirección'
/>
<span
className='input-group-addon'
ref='domain'
>
</span>
</div>
</div>
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Nombre'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
ref='displayName'
placeholder='Nombre mostrado en contactos'
/>
</div>
</div>
<div className='form-group'>
<div className='col-sm-3'></div>
<div className='col-sm-8'>
<Button
btnAttrs={
{
className: 'btn btn-info action-edit-dl',
onClick: (e) => {
this.saveChanges(e);
}
}
}
>
{'Guardar'}
</Button>
<Button
btnAttrs={
{
className: 'btn btn-default action-button',
onClick: (e) => {
Utils.handleLink(e, `/domains/${this.props.params.domain_id}/distribution_lists/${this.props.params.id}`);
}
}
}
>
{'Cancelar'}
</Button>
</div>
</div>
</form>
);
actions = [
{
label: 'Guardar',
props: {
className: 'btn btn-info btn-xs action-edit-dl',
onClick: (e) => {
this.saveChanges(e);
}
}
},
{
label: 'Cancelar',
props: {
className: 'btn btn-default btn-xs action-button',
onClick: (e) => {
Utils.handleLink(e, `/domains/${this.props.params.domain_id}/distribution_lists/${this.props.params.id}`);
}
}
}
];
return (
<div>
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-12 central-content'>
<Panel
title={'Editar Lista de Distribución'}
btnsHeader={actions}
classHeader={'forum-box'}
>
{form}
</Panel>
</div>
</div>
</div>
</div>
);
}
}
EditDistributionList.propTypes = {
location: React.PropTypes.object,
params: React.PropTypes.any
};
...@@ -71,7 +71,8 @@ export default class AddAdminModal extends React.Component { ...@@ -71,7 +71,8 @@ export default class AddAdminModal extends React.Component {
this.props.domain.addAdmin( this.props.domain.addAdmin(
user.name, user.name,
(error) => { (error, success) => {
console.log(error, success);
if (error) { if (error) {
return this.setState({ return this.setState({
error: { error: {
......
This diff is collapsed.
...@@ -8,10 +8,14 @@ import MessageBar from '../message_bar.jsx'; ...@@ -8,10 +8,14 @@ import MessageBar from '../message_bar.jsx';
import PageInfo from '../page_info.jsx'; import PageInfo from '../page_info.jsx';
import PanelTab from '../panel_tab.jsx'; import PanelTab from '../panel_tab.jsx';
import Button from '../button.jsx';
import DomainGeneralInfo from './domain_general_info.jsx'; import DomainGeneralInfo from './domain_general_info.jsx';
import DomainMailboxPlans from './domain_mailbox_plans.jsx'; import DomainMailboxPlans from './domain_mailbox_plans.jsx';
import DomainAdmins from './domain_admin_list.jsx'; import DomainAdmins from './domain_admin_list.jsx';
import DomainDistributionList from './domain_distribution_list.jsx'; import DomainDistributionList from './domain_distribution_list.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import MultipleTaskModal from './multiple_task_modal.jsx';
import AntiSpamComponent from './antispam.jsx';
import DomainStore from '../../stores/domain_store.jsx'; import DomainStore from '../../stores/domain_store.jsx';
...@@ -93,12 +97,42 @@ export default class DomainDetails extends React.Component { ...@@ -93,12 +97,42 @@ export default class DomainDetails extends React.Component {
/> />
); );
const tabTareasMasivas = (
<div>
<ul className='list-inline'>
<li>
<ToggleModalButton
role='button'
className=''
dialogType={MultipleTaskModal}
dialogProps={{
data: domain
}}
key='change-passwd-import'
>
{'Mensaje fuera de Oficina'}
</ToggleModalButton>
</li>
</ul>
</div>
);
const tabAntiSpam = (
<div>
<AntiSpamComponent
data={this.state.domain}
/>
</div>
);
const panelTabs = ( const panelTabs = (
<PanelTab <PanelTab
tabNames={['Administradores', 'Listas De Distribución']} tabNames={['Administradores', 'AntiSpam', 'Listas De Distribución', 'Tareas Masivas']}
tabs={{ tabs={{
administradores: tabAdmin, administradores: tabAdmin,
listas_de_distribución: tabDistribution antispam: tabAntiSpam,
listas_de_distribución: tabDistribution,
tareas_masivas: tabTareasMasivas
}} }}
location={this.props.location} location={this.props.location}
/> />
...@@ -113,19 +147,27 @@ export default class DomainDetails extends React.Component { ...@@ -113,19 +147,27 @@ export default class DomainDetails extends React.Component {
{message} {message}
<div className='content animate-panel'> <div className='content animate-panel'>
<div className='row'> <div className='row'>
<div className='col-md-6 central-content'> <div className='layout-back clearfix'>
<DomainGeneralInfo <div className='back-left backstage'>
domain={domain} <div className='backbg'></div>
location={this.props.location} </div>
params={this.props.params} <div className='back-right backstage'>
/> <div className='backbg'></div>
</div> </div>
<div className='col-md-6 central-content'> <div className='col-md-6 central-content'>
<DomainMailboxPlans <DomainGeneralInfo
domain={domain} domain={domain}
location={this.props.location} location={this.props.location}
params={this.props.params} params={this.props.params}
/> />
</div>
<div className='col-md-6 central-content'>
<DomainMailboxPlans
domain={domain}
location={this.props.location}
params={this.props.params}
/>
</div>
</div> </div>
</div> </div>
<div className='row'> <div className='row'>
......
...@@ -23,6 +23,7 @@ export default class DomainDistributionList extends React.Component { ...@@ -23,6 +23,7 @@ export default class DomainDistributionList extends React.Component {
} }
getStateFromStores() { getStateFromStores() {
const lists = DomainStore.getDistributionLists(this.props.domain); const lists = DomainStore.getDistributionLists(this.props.domain);
return { return {
lists lists
}; };
...@@ -107,7 +108,7 @@ export default class DomainDistributionList extends React.Component { ...@@ -107,7 +108,7 @@ export default class DomainDistributionList extends React.Component {
<td className='distribution-list-actions'> <td className='distribution-list-actions'>
<a <a
href='#' href='#'
onClick={(e) => Utils.handleLink(e, `/distribution_lists/${dl.id}`)} onClick={(e) => Utils.handleLink(e, `/domains/${domain.id}/distribution_lists/${dl.id}`)}
> >
{'Ver'} {'Ver'}
</a> </a>
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import DateTimeField from 'react-bootstrap-datetimepicker';
import * as Client from '../../utils/client.jsx';
import Promise from 'bluebird';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
export default class MultipleTaskModal extends React.Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.getOwnAccounts = this.getOwnAccounts.bind(this);
this.handleChangeDate = this.handleChangeDate.bind(this);
this.state = {
loaded: false
};
}
handleChangeDate() {
const timeLapse = 100;
setTimeout(() => {
const formated = document.getElementById('zimbraPrefOutOfOfficeUntilDate').value.split('/').reverse().join('') + '000000Z';
this.refs.zimbraPrefOutOfOfficeUntilDate.value = formated;
}, timeLapse);
}
onSubmit() {
const accounts = this.state.accounts.account;
const domain = this.props.data;
const total = accounts.length;
const collection = [];
GlobalActions.emitStartTask({
origin: 'Dominio - Tareas Masivas',
id: 'dominio-massive-task',
action: `Asignando mensaje masivo fuera de oficina a ${total}`
});
const attrs = {};
const isEnabled = this.refs.zimbraPrefOutOfOfficeReplyEnabled.checked;
if (isEnabled) {
attrs.zimbraPrefOutOfOfficeReplyEnabled = isEnabled.toString().toUpperCase();
attrs.zimbraPrefOutOfOfficeReply = this.refs.zimbraPrefOutOfOfficeReply.value;
attrs.zimbraPrefOutOfOfficeUntilDate = this.refs.zimbraPrefOutOfOfficeUntilDate.value;
} else {
attrs.zimbraPrefOutOfOfficeReplyEnabled = isEnabled.toString().toUpperCase();
}
accounts.forEach((account) => {
collection.push(Client.modifyAccountByBatch(account.id, attrs));
});
Client.batchRequest(collection, () => {
GlobalActions.emitEndTask({
id: 'dominio-massive-task',
toast: {
message: `Se han agregado el mensaje fuera de oficina con exito a las ${total} casillas del dominio: ${domain.name}`,
title: 'Dominio - Mensaje Masivo'
}
});
if (this.props.show) {
this.props.onHide();
}
}, (err) => {
console.log('err',err);
if (this.props.show) {
this.props.onHide();
}
});
}
getOwnAccounts() {
const domain = this.props.data;
new Promise((resolve, reject) => {
Client.getAllAccounts({
domain: domain.name
}, (success) => {
return resolve(success);
}, (err) => {
return reject(err);
});
}).then((res) => {
this.setState({
loaded: true,
accounts: res
});
}).catch((error) => {
console.log('err', error);
}).finally(() => {
});
}
componentDidMount() {
this.getOwnAccounts();
}
render() {
let content = null;
if (this.state.loaded) {
content = (
<form
className='simple_form form-horizontal mailbox-form'
id='createAccount'
>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Habilitado'}
</label>
<div className='col-sm-8'>
<label className='radio-inline pretty-input'>
<div className='pretty-checkbox'>
<input
type='checkbox'
className='pretty'
ref='zimbraPrefOutOfOfficeReplyEnabled'
/>
<span></span>
</div>
</label>
</div>
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Termina el'}
</label>
<div className='col-sm-8'>
<DateTimeField
inputFormat='DD/MM/YYYY'
inputProps={
{
id: 'zimbraPrefOutOfOfficeUntilDate',
readOnly: 'readOnly'
}
}
onChange={this.handleChangeDate}
mode={'date'}
/>
<input
type='hidden'
ref='zimbraPrefOutOfOfficeUntilDate'
/>
</div>
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Respuesta'}
</label>
<div className='col-sm-8'>
<textarea
name='response'
id='responseBox'
className='form-control'
rows='4'
ref='zimbraPrefOutOfOfficeReply'
>
</textarea>
</div>
</div>
</form>
);
} else {
content = (
<div className='text-center'>
<i className='fa fa-spinner fa-spin fa-3x fa-fw'></i>
<p>Cargando...</p>
</div>
);
}
return (
<Modal
show={this.props.show}
onHide={this.props.onHide}
bsSize={'lg'}
>
<div className='color-line'></div>
<Modal.Header closeButton={true}>
<Modal.Title>
{'Tareas Masivas'}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
{content}
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={this.props.onHide}
>
{'Cancelar'}
</button>
<button
className='btn btn-info action-massive'
type='button'
onClick={this.onSubmit}
>
{'Guardar'}
</button>
</Modal.Footer>
</Modal>
);
}
}
MultipleTaskModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
data: React.PropTypes.object
};
This diff is collapsed.
...@@ -5,6 +5,7 @@ import LoadingScreen from './loading_screen.jsx'; ...@@ -5,6 +5,7 @@ import LoadingScreen from './loading_screen.jsx';
import Header from './header.jsx'; import Header from './header.jsx';
import Sidebar from './sidebar.jsx'; import Sidebar from './sidebar.jsx';
import ToastAlert from './toast_alert.jsx'; import ToastAlert from './toast_alert.jsx';
import ProgressTask from './progress_task.jsx';
import React from 'react'; import React from 'react';
...@@ -12,6 +13,7 @@ export default class LoggedIn extends React.Component { ...@@ -12,6 +13,7 @@ export default class LoggedIn extends React.Component {
render() { render() {
return ( return (
<div> <div>
<ProgressTask/>
<ToastAlert/> <ToastAlert/>
<LoadingScreen/> <LoadingScreen/>
<Header/> <Header/>
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved. // Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react';
import {browserHistory} from 'react-router'; import {browserHistory} from 'react-router';
import {Modal} from 'react-bootstrap'; import {Modal} from 'react-bootstrap';
import UserStore from '../../stores/user_store.jsx'; import UserStore from '../../stores/user_store.jsx';
import PasswordStrengthMeter from 'react-password-strength-meter';
import React from 'react';
export default class ConfirmDeleteModal extends React.Component { export default class ConfirmDeleteModal extends React.Component {
constructor(props) { constructor(props) {
...@@ -13,10 +13,24 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -13,10 +13,24 @@ export default class ConfirmDeleteModal extends React.Component {
this.handleChangePasswd = this.handleChangePasswd.bind(this); this.handleChangePasswd = this.handleChangePasswd.bind(this);
this.forceLogout = this.forceLogout.bind(this); this.forceLogout = this.forceLogout.bind(this);
this.handlePasswd = this.handlePasswd.bind(this);
this.restart = this.restart.bind(this);
this.state = this.getStateFromStores(); this.state = this.getStateFromStores();
} }
restart() {
this.setState({
message: null
});
}
handlePasswd(e) {
const hidePasswd = this.refs.passwdfield;
hidePasswd.value = e.target.value;
}
getStateFromStores() { getStateFromStores() {
const currentUser = UserStore.getCurrentUser(); const currentUser = UserStore.getCurrentUser();
return {currentUser}; return {currentUser};
...@@ -30,13 +44,24 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -30,13 +44,24 @@ export default class ConfirmDeleteModal extends React.Component {
handleChangePasswd() { handleChangePasswd() {
if (this.refs.passwdfield.value && this.refs.passwdfield.value.length > 0) { if (this.refs.passwdfield.value && this.refs.passwdfield.value.length > 0) {
if (this.refs.passwdfield.value.length < 9) {
return this.setState({
alert: true,
message: 'Su contraseña debe ser mayor a 8 caracteres.',
typeError: 'text-danger'
});
}
this.props.data.setPassword(this.refs.passwdfield.value, () => { this.props.data.setPassword(this.refs.passwdfield.value, () => {
this.setState({ this.setState({
alert: true, alert: true,
message: 'Su contraseña se ha sido cambiada éxitosamente.', message: 'Su contraseña se ha sido cambiada éxitosamente.',
typeError: 'text-success' typeError: 'text-success'
}); });
this.forceLogout('/logout');
if (this.props.data.name === this.state.currentUser.name) {
this.forceLogout('/logout');
}
}, (error) => { }, (error) => {
this.setState({ this.setState({
alert: true, alert: true,
...@@ -66,6 +91,9 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -66,6 +91,9 @@ export default class ConfirmDeleteModal extends React.Component {
<Modal <Modal
show={this.props.show} show={this.props.show}
onHide={this.props.onHide} onHide={this.props.onHide}
onExit={() => {
this.restart();
}}
> >
<div className='color-line'></div> <div className='color-line'></div>
<Modal.Header closeButton={true}> <Modal.Header closeButton={true}>
...@@ -82,11 +110,26 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -82,11 +110,26 @@ export default class ConfirmDeleteModal extends React.Component {
</div> </div>
<div className='col-xs-7'> <div className='col-xs-7'>
<input <input
type='password' type='hidden'
ref='passwdfield' ref='passwdfield'
className='form-control' className='form-control'
id='passwdfield' id='passwdfield'
/> />
<PasswordStrengthMeter
passwordText=''
className='form-control passwd-field'
hasLabel={false}
hasSuggestion={false}
hasWarning={true}
warning='Su contraseña debe ser mayor a 8 caracteres.'
onChange={this.handlePasswd}
strength={{
0: 'Su contraseña es muy debil',
1: 'Debe incrementar la dificultad de su contraseña',
2: 'Su contraseña es relativamente fuerte',
3: 'Su contraseña es fuerte'
}}
/>
</div> </div>
<div className='col-xs-12 text-center'> <div className='col-xs-12 text-center'>
<br/> <br/>
......
...@@ -5,6 +5,9 @@ import {browserHistory} from 'react-router'; ...@@ -5,6 +5,9 @@ import {browserHistory} from 'react-router';
import {Modal} from 'react-bootstrap'; import {Modal} from 'react-bootstrap';
import * as Utils from './../../utils/utils.jsx'; import * as Utils from './../../utils/utils.jsx';
import * as Client from './../../utils/client.jsx'; import * as Client from './../../utils/client.jsx';
import Promise from 'bluebird';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import MailboxStore from '../../stores/mailbox_store.jsx';
import React from 'react'; import React from 'react';
...@@ -30,31 +33,45 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -30,31 +33,45 @@ export default class ConfirmDeleteModal extends React.Component {
} }
handleDelete() { handleDelete() {
Client.removeAccount( new Promise((resolve, reject) => {
this.props.data.id, // start loading
() => { GlobalActions.emitStartLoading();
this.setState(
{ Client.removeAccount(
alert: true, this.props.data.id,
message: `Su cuenta ${this.props.data.attrs.mail}, ha sido eliminada éxitosamente. Sera redirigido en ${this.state.timeToRedirect} seg`, () => {
typeError: 'text-success' return resolve(true);
} },
); (error) => {
this.redirect(); return reject(error);
}, }
() => { );
this.setState( }).then(() => {
{ MailboxStore.removeAccount(this.props.data);
alert: true, this.setState(
message: `Hubo un error, al intentar eliminar su cuenta : ${this.props.data.attrs.mail}`, {
typeError: 'text-danger' alert: true,
} message: `Su cuenta ${this.props.data.attrs.mail}, ha sido eliminada éxitosamente. Sera redirigido en ${this.state.timeToRedirect} seg`,
); typeError: 'text-warning'
} }
); );
Utils.toggleStatusButtons('.close-modal', true);
this.redirect();
}).catch((error) => {
this.setState(
{
alert: true,
message: error.message,
typeError: 'text-edanger'
}
);
}).finally(() => {
GlobalActions.emitEndLoading();
});
} }
redirect() { redirect() {
const redirectAt = 1000;
setTimeout(() => { setTimeout(() => {
if (this.timetoRedict-- < 1) { if (this.timetoRedict-- < 1) {
browserHistory.replace('/mailboxes'); browserHistory.replace('/mailboxes');
...@@ -65,7 +82,7 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -65,7 +82,7 @@ export default class ConfirmDeleteModal extends React.Component {
this.redirect(); this.redirect();
} }
}, 1000); }, redirectAt);
} }
handleKeyUp() { handleKeyUp() {
...@@ -120,7 +137,7 @@ export default class ConfirmDeleteModal extends React.Component { ...@@ -120,7 +137,7 @@ export default class ConfirmDeleteModal extends React.Component {
<Modal.Footer> <Modal.Footer>
<button <button
type='button' type='button'
className='btn btn-default' className='btn btn-default close-modal'
onClick={this.props.onHide} onClick={this.props.onHide}
> >
{'Cancelar'} {'Cancelar'}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -30,12 +30,12 @@ export default class FormVacacionesMailbox extends React.Component { ...@@ -30,12 +30,12 @@ export default class FormVacacionesMailbox extends React.Component {
Client.modifyAccount(data.id, attrs, () => { Client.modifyAccount(data.id, attrs, () => {
GlobalActions.emitMessage({ GlobalActions.emitMessage({
message: 'Se ha modificado con éxito', error: 'Se ha modificado su respuesta de vacaciones con éxito.',
type: messageType.SUCCESS type: messageType.SUCCESS
}); });
}, (error) => { }, (error) => {
GlobalActions.emitMessage({ GlobalActions.emitMessage({
message: error.message, error: error.message,
type: messageType.ERROR type: messageType.ERROR
}); });
}); });
......
import React from 'react'; import React from 'react';
import Button from '../button.jsx'; import Button from '../button.jsx';
import StatusLabel from '../status_label.jsx';
import * as Client from '../../utils/client.jsx'; import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx'; import * as Utils from '../../utils/utils.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import ZimbraStore from '../../stores/zimbra_store.jsx';
export default class BlockGeneralInfoMailbox extends React.Component { export default class BlockGeneralInfoMailbox extends React.Component {
constructor(props) { constructor(props) {
...@@ -16,24 +19,22 @@ export default class BlockGeneralInfoMailbox extends React.Component { ...@@ -16,24 +19,22 @@ export default class BlockGeneralInfoMailbox extends React.Component {
this.state = {}; this.state = {};
} }
componentWillMount() {
this.domain = this.props.data.name.split('@');
this.domain = this.domain[this.domain.length - 1];
}
componentDidMount() { componentDidMount() {
this.getDomain(); this.getDomain();
} }
getDomain() { getDomain() {
Client.getDomain(this.domain, (data) => { const domain = Utils.getDomainFromString(this.data.name);
Client.getDomain(domain, (data) => {
this.setState({ this.setState({
hasDomain: true, hasDomain: true,
domainData: data domainData: data
}); });
}, (error) => { }, (error) => {
this.setState({ GlobalActions.emitMessage({
error: error.message error: error.message,
typeError: error.type
}); });
}); });
} }
...@@ -44,25 +45,64 @@ export default class BlockGeneralInfoMailbox extends React.Component { ...@@ -44,25 +45,64 @@ export default class BlockGeneralInfoMailbox extends React.Component {
render() { render() {
let blockInfo = null; let blockInfo = null;
let statusCos = null;
const cosID = Utils.getEnabledPlansObjectByCos(ZimbraStore.getAllCos(), this.props.data.attrs.zimbraCOSId);
let cosName = null;
switch (cosID.name) {
case 'premium':
cosName = Utils.titleCase(cosID.name);
statusCos = 'label btn-primary2 btn-xs';
break;
case 'professional':
cosName = Utils.titleCase(cosID.name);
statusCos = 'label btn-primary btn-xs';
break;
case 'basic':
cosName = Utils.titleCase(cosID.name);
statusCos = 'label btn-success btn-xs';
break;
default:
cosName = 'Sin Plan';
statusCos = 'label noplan btn-xs';
break;
}
if (this.state.hasDomain) { if (this.state.hasDomain) {
const data = this.props.data;
const attrs = this.props.data.attrs;
const owner = (!attrs.givenName || !attrs.sn) ? null : attrs.givenName + ' ' + attrs.sn;
const mail = data.name;
//properties
const description = attrs.description;
const mailhost = attrs.zimbraMailHost;
const archive = attrs.zimbraArchiveAccount;
blockInfo = ( blockInfo = (
<article> <article className='account-info'>
<div> <div>
<h4> <h4>
<span className='mailbox-name text-success'>{this.data.name}</span> <span className='mailbox-name text-success'>{mail}</span>
</h4> </h4>
{owner && (
<h5>
{owner}
</h5>
)}
</div> </div>
<div> {description && (
<p> <div>
{this.data.attrs.description} <p>
</p> {description}
</div> </p>
</div>
)}
<div> <div>
<p> <p>
<strong>{'Dominio'}: </strong> <strong>{'Dominio: '}</strong>
<Button <Button
btnAttrs={{ btnAttrs={{
onClick: (e) => { onClick: (e) => {
...@@ -70,10 +110,36 @@ export default class BlockGeneralInfoMailbox extends React.Component { ...@@ -70,10 +110,36 @@ export default class BlockGeneralInfoMailbox extends React.Component {
} }
}} }}
> >
{this.domain} {this.state.domainData.name}
</Button> </Button>
</p> </p>
</div> </div>
{archive && (
<div>
<p>
<strong>{'Archive: '}</strong>
{archive}
</p>
</div>
)}
{mailhost && (
<div>
<p>
<strong>{'Mail Host'}: </strong>
{mailhost}
</p>
</div>
)}
<div>
<p>
<StatusLabel classes={statusCos}>
{cosName}
</StatusLabel>
</p>
</div>
</article> </article>
); );
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -112,7 +112,7 @@ MessageBar.defaultProps = { ...@@ -112,7 +112,7 @@ MessageBar.defaultProps = {
MessageBar.propTypes = { MessageBar.propTypes = {
message: React.PropTypes.string.isRequired, message: React.PropTypes.string.isRequired,
type: React.PropTypes.oneOf(['SUCCESS', 'ERROR', 'WARNING', 'INFO']), type: React.PropTypes.oneOf(['SUCCESS', 'ERROR', 'WARNING', 'INFO', 'LOCKED']),
position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']), position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']),
canClose: React.PropTypes.bool, canClose: React.PropTypes.bool,
autoclose: React.PropTypes.bool, autoclose: React.PropTypes.bool,
......
...@@ -123,7 +123,7 @@ export default class Pagination extends React.Component { ...@@ -123,7 +123,7 @@ export default class Pagination extends React.Component {
return ( return (
<div id='pagination'> <div id='pagination'>
<ul className='pagination'> <ul className='pagination pagination-sm'>
{first} {first}
{prev} {prev}
{pages} {pages}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -13,6 +13,15 @@ export default class Panel extends React.Component { ...@@ -13,6 +13,15 @@ export default class Panel extends React.Component {
}; };
} }
componentWillReceiveProps(nextProps) {
if (Object.keys(nextProps.tabs)[0] !== Object.keys(this.props.tabs)[0]) {
const tab = this.props.location.query.tab || Object.keys(nextProps.tabs)[0];
this.setState({
tab
});
}
}
changeTab(e, tabName) { changeTab(e, tabName) {
e.preventDefault(); e.preventDefault();
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment