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
* **debug**: Use in development environmet to recieve messages in the javascript console.
* **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) ).
* **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).
......@@ -99,6 +99,20 @@ Posible status are:
| 2 | Vencida |
| 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
* 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 @@
"react-bootstrap-datetimepicker": "0.0.22",
"react-datalist": "^3.0.0",
"react-dom": "15.0.1",
"react-password-strength-meter": "^1.0.5",
"react-router": "2.3.0",
"react-textarea-autosize": "4.0.0",
"react-toastr": "^2.6.0",
"toastr": "^2.1.2",
"zimbra-admin-api-js": "ZBoxApp/zimbra-admin-api-js#master"
"zimbra-admin-api-js": "ZBoxApp/zimbra-admin-api-js"
},
"devDependencies": {
"babel-cli": "^6.7.5",
......@@ -62,7 +63,7 @@
},
"scripts": {
"check": "eslint --ext \".jsx\" --ignore-pattern node_modules --quiet .",
"build": "webpack",
"build": "NODE_ENV=production webpack",
"run": "webpack --progress --watch",
"run-fullmap": "webpack --progress --watch",
"companies-service": "babel-node companies-service.js",
......
......@@ -11,12 +11,26 @@ export function emitStartLoading() {
});
}
export function emitStartTask(params) {
AppDispatcher.handleViewAction({
type: ActionTypes.START_TASK_LOADING,
params
});
}
export function emitEndLoading() {
AppDispatcher.handleViewAction({
type: ActionTypes.END_LOADING
});
}
export function emitEndTask(params) {
AppDispatcher.handleViewAction({
type: ActionTypes.END_TASK_LOADING,
params
});
}
export function emitMessage(attrs) {
AppDispatcher.handleViewAction({
type: ActionTypes.NEW_MESSAGE,
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import $ from 'jquery';
import React from 'react';
import EventStore from '../../stores/event_store.jsx';
import PageInfo from '../page_info.jsx';
import PanelTab from '../panel_tab.jsx';
import Button from '../button.jsx';
import Panel from '../panel.jsx';
import ActionsPanel from '../panel_actions.jsx';
import ActionsPanelAllows from '../panel_actions_allows.jsx';
import MessageBar from '../message_bar.jsx';
import Promise from 'bluebird';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import DomainStore from '../../stores/domain_store.jsx';
import Constants from '../../utils/constants.jsx';
const MessagesType = Constants.MessageType;
export default class DistributionLists extends React.Component {
constructor(props) {
super(props);
this.getDistributionLists = this.getDistributionLists.bind(this);
this.showMessage = this.showMessage.bind(this);
this.onDeleteMember = this.onDeleteMember.bind(this);
this.onDeleteOwner = this.onDeleteOwner.bind(this);
this.onSubmitMembers = this.onSubmitMembers.bind(this);
this.onCancelMember = this.onCancelMember.bind(this);
this.onCancelOwner = this.onCancelOwner.bind(this);
this.domain = null;
this.state = {};
}
showMessage(attrs) {
const autoTimer = 10;
this.setState({
error: attrs.error,
type: attrs.typeError,
autocloseInSecs: autoTimer
});
}
getDistributionLists() {
const id = this.props.params.id;
const response = {};
const domain = DomainStore.getCurrent();
return new Promise((resolve, reject) => {
if (domain) {
response.domain = domain;
const dl = DomainStore.getDistributionListById(id, domain);
console.log(domain, dl);
response.distributionsList = dl;
dl.getOwners((error, owners) => {
if (owners) {
response.owners = owners;
return resolve(response);
}
return reject(error);
});
}
return Client.getDistributionList(id, (success) => {
const distributionsList = success;
response.distributionsList = distributionsList;
const domainId = this.props.params.domain_id;
distributionsList.getOwners((error, owners) => {
if (owners) {
response.owners = owners;
Client.getDomain(domainId, (doma) => {
response.domain = doma;
resolve(response);
}, (err) => {
reject(err);
});
}
});
}, (error) => {
reject(error);
});
}).then((data) => {
DomainStore.setOwners(Utils.getOwners(data.owners));
DomainStore.setMembers(data.distributionsList.members);
this.setState({
distributionsList: data.distributionsList,
members: DomainStore.getMembers(),
owners: DomainStore.getOwners(),
domain: data.domain
});
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
typeError: MessagesType.ERROR
});
}).finally(() => {
GlobalActions.emitEndLoading();
});
}
onSubmitOwners(response) {
if (response.refresh) {
response.refresh.forEach((member) => {
DomainStore.addOwners(member);
});
}
this.multipleActionsOwners(response, this.state.distributionsList).then((res) => {
const newOwners = DomainStore.getOwners();
const errors = [];
const limit = res.length;
for (let i = 0; i < limit; i++) {
const items = res[i];
if (items.error) {
const action = (items.action === 'remove') ? 'eliminar' : 'agregar';
errors.push({
error: `Hubo un error al ${action} ${items.item}, debido a : ${items.error.extra.reason}`,
type: MessagesType.ERROR
});
}
}
if (errors.length !== limit) {
errors.push({
error: 'Se han guardado los datos para permitidos éxitosamente.',
type: MessagesType.SUCCESS
});
}
this.setState({
owners: newOwners,
error: errors
});
response.reset();
});
}
onSubmitMembers(response) {
if (response.refresh) {
response.refresh.forEach((member) => {
DomainStore.addMember(member);
});
}
this.multipleActionsMembers(response, this.state.distributionsList).then((res) => {
const newMembers = DomainStore.getMembers();
const errors = [];
const limit = res.length;
for (let i = 0; i < limit; i++) {
const items = res[i];
if (items.error) {
const action = (items.action === 'remove') ? 'eliminar' : 'agregar';
errors.push({
error: `Hubo un error al ${action} ${items.item}, debido a : ${items.error.extra.reason}`,
type: MessagesType.ERROR
});
}
}
if (errors.length !== limit) {
errors.push({
error: 'Se han guardado los datos para miembros éxitosamente.',
type: MessagesType.SUCCESS
});
}
this.setState({
members: newMembers,
error: errors
});
response.reset();
});
}
multipleActionsMembers(response, account) {
const promises = [];
if (response.add || response.remove) {
if (response.add) {
const add = new Promise((resolve) => {
account.addMembers(response.add, (error) => {
const res = {};
if (error) {
res.isCompleted = false;
res.action = 'add';
res.error = error;
return resolve(res);
}
response.add.forEach((member) => {
DomainStore.addMember(member);
});
res.isCompleted = true;
res.action = 'add';
return resolve(res);
});
});
promises.push(add);
}
if (response.remove) {
const remove = new Promise((resolve) => {
account.removeMembers(response.remove, (error) => {
const res = {};
if (error) {
res.isCompleted = false;
res.action = 'remove';
res.error = error;
return resolve(res);
}
response.remove.forEach((member) => {
DomainStore.removeMember(member);
});
res.isCompleted = true;
res.action = 'remove';
return resolve(res);
});
});
promises.push(remove);
}
}
return Promise.all(promises);
}
multipleActionsOwners(response, account) {
const promises = [];
for (const key in response) {
if (response.hasOwnProperty(key) && key === 'add') {
const array = response[key];
const limit = array.length;
for (let index = 0; index < limit; index++) {
const res = {};
const promesa = new Promise((resolve) => {
account.addOwner(array[index], (error) => {
if (error) {
res.isCompleted = false;
res.item = response[key][index];
res.action = key;
res.error = error;
} else {
res.isCompleted = true;
res.item = response[key][index];
res.action = key;
DomainStore.addOwners(response[key][index]);
}
return resolve(res);
});
});
promises.push(promesa);
}
}
if (response.hasOwnProperty(key) && key === 'remove') {
const array = response[key];
const limit = array.length;
for (let index = 0; index < limit; index++) {
const res = {};
const promesa = new Promise((resolve) => {
account.removeOwner(array[index], (error) => {
if (error) {
res.isCompleted = false;
res.item = response[key][index];
res.action = key;
res.error = error;
} else {
res.isCompleted = true;
res.item = response[key][index];
res.action = key;
}
return resolve(res);
});
});
promises.push(promesa);
}
}
}
return Promise.all(promises);
}
onDeleteMember(member) {
DomainStore.removeMember(member);
const currentMembers = DomainStore.getMembers();
this.setState({
members: currentMembers,
error: false
});
}
onCancelMember(response) {
if (response && response.length > 0) {
response.forEach((member) => {
DomainStore.addMember(member);
});
const newMembers = DomainStore.getMembers();
this.setState({
members: newMembers,
error: false
});
}
return null;
}
onDeleteOwner(owner) {
DomainStore.removeOwner(owner);
const currentOwners = DomainStore.getOwners();
this.setState({
owners: currentOwners,
error: false
});
}
onCancelOwner(response) {
if (response && response.length > 0) {
response.forEach((member) => {
DomainStore.addOwners(member);
});
const newOwners = DomainStore.getOwners();
this.setState({
owners: newOwners,
error: false
});
}
return null;
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
$('#sidebar-domains').addClass('active');
this.getDistributionLists();
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
$('#sidebar-domains').removeClass('active');
}
render() {
let generalData;
let domainInfo;
let btnsGeneralInfo = [];
let actionTabsAllows = [];
let actionTabsMembers = [];
let message;
let panelTabs;
let isPrivate = null;
console.log(this.state);
if (this.state.distributionsList && this.state.owners) {
const data = this.state.distributionsList;
const domain = this.state.domain;
const owners = this.state.owners;
const arrayMembers = this.state.members;
const membersFormatted = Utils.getMembers(data.members, 'Miembros');
if (owners.length > 0) {
isPrivate = (
<span className='label label-danger'>
{'Privada'}
</span>
);
}
generalData = (
<div>
<h4>{data.name}</h4>
<p>{membersFormatted}</p>
{isPrivate}
</div>
);
domainInfo = (
<div>
<h4>
<Button
btnAttrs={
{
className: 'text-success domain-name',
onClick: (e) => {
Utils.handleLink(e, `/domains/${domain.id}`);
}
}
}
>
{`@${domain.name}`}
</Button>
</h4>
</div>
);
btnsGeneralInfo = [
{
props: {
className: 'btn btn-xs btn-default',
onClick: (e) => {
Utils.handleLink(e, `/domains/${this.props.params.domain_id}/distribution_lists/${this.props.params.id}/edit`, this.props.location);
}
},
label: 'Editar'
}
];
actionTabsMembers = (
<ActionsPanel
name={'Miembros'}
data={arrayMembers}
onApplyChanges={(response) => {
this.onSubmitMembers(response);
}}
hasExport={true}
isEmail={true}
onDelete={(member) => {
this.onDeleteMember(member);
}}
onCancel={(response) => {
this.onCancelMember(response);
}}
/>
);
actionTabsAllows = (
<ActionsPanelAllows
name={'Permitidos'}
data={owners}
onApplyChanges={(response) => {
this.onSubmitOwners(response);
}}
hasExport={true}
isEmail={true}
onDelete={(owner) => {
this.onDeleteOwner(owner);
}}
onCancel={(response) => {
this.onCancelOwner(response);
}}
/>
);
}
if (this.state.error) {
if (Array.isArray(this.state.error)) {
message = this.state.error.map((err, i) => {
return (
<MessageBar
key={`new-error-${i}`}
message={err.error}
type={err.type}
autoclose={true}
/>
);
});
} else {
message = (
<MessageBar
message={this.state.error}
type={this.state.type}
autoclose={true}
/>
);
}
}
const pageInfo = (
<PageInfo
titlePage='lista de distribución'
descriptionPage='Para enviar correo a grupos de usuarios'
/>
);
const allows = (
<Panel
title='Permitidos'
hasHeader={false}
classHeader={'reset-panel'}
children={actionTabsAllows}
/>
);
const members = (
<Panel
title='Miembres'
hasHeader={false}
classHeader={'reset-panel'}
children={actionTabsMembers}
/>
);
panelTabs = (
<PanelTab
tabNames={['Miembros', 'Permitidos']}
tabs={{
miembros: members,
permitidos: allows
}}
location={this.props.location}
/>
);
if (this.state.notFound) {
return (
<div>
{pageInfo}
<div className='block-center text-center'>
<h3>{this.state.message}</h3>
</div>
</div>
);
}
return (
<div>
{pageInfo}
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-6 central-content'>
<Panel
title='Información General'
btnsHeader={btnsGeneralInfo}
children={generalData}
classHeader='with-min-height'
/>
</div>
<div className='col-md-6 central-content'>
<Panel
title='Dominio'
children={domainInfo}
classHeader='with-min-height'
/>
</div>
</div>
<div className='row'>
<div className='col-md-12 panel-with-tabs'>
{panelTabs}
</div>
</div>
</div>
</div>
);
}
}
DistributionLists.propTypes = {
location: React.PropTypes.object,
params: React.PropTypes.object
};
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 {
this.props.domain.addAdmin(
user.name,
(error) => {
(error, success) => {
console.log(error, success);
if (error) {
return this.setState({
error: {
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import Button from '../button.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Promise from 'bluebird';
import DomainStore from '../../stores/domain_store.jsx';
export default class AntiSpam extends React.Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
this.handleSave = this.handleSave.bind(this);
this.alterDomain = this.alterDomain.bind(this);
this.domain = DomainStore.getCurrent();
console.log(this.domain);
this.blackList = Array.isArray(this.domain.attrs.amavisBlacklistSender) ? this.domain.attrs.amavisBlacklistSender : this.domain.attrs.amavisBlacklistSender.trim().split(' ');
this.whiteList = Array.isArray(this.domain.attrs.amavisWhitelistSender) ? this.domain.attrs.amavisWhitelistSender : this.domain.attrs.amavisWhitelistSender.trim().split(' ');
}
handleDelete(e, item, action) {
e.preventDefault();
const attrs = {};
switch (action) {
case 'black':
this.searchItemToRemove(this.blackList, item);
attrs.amavisBlacklistSender = this.blackList;
break;
case 'white':
this.searchItemToRemove(this.whiteList, item);
attrs.amavisWhitelistSender = this.whiteList;
break;
}
this.alterDomain(attrs);
}
searchItemToRemove(array, item) {
const total = array.length;
for (let i = 0; i < total; i++) {
if (array[i] === item) {
array.splice(i, 1);
break;
}
}
}
handleSave(e, action) {
const attrs = {};
const isEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const isDomain = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/;
e.preventDefault();
const input = this.refs[`${action}-item`];
const value = input.value.trim();
if (!value || value === '') {
console.log('no hay valores');
return false;
}
const isValid = isEmail.test(value) || isDomain.test(value);
if (!isValid) {
console.log('es invalido');
return false;
}
switch (action) {
case 'black':
this.blackList.push(value);
attrs.amavisBlacklistSender = this.blackList;
break;
case 'white':
this.whiteList.push(value);
attrs.amavisWhitelistSender = this.whiteList;
break;
}
this.alterDomain(attrs, e.target, input);
}
alterDomain(attrs, button, input) {
const domain = this.domain;
const hasButton = button || false;
const hasInput = input || false;
let oldContent = null;
let id = null;
if (hasButton) {
oldContent = button.innerHTML;
id = `#${button.getAttribute('id')}`;
}
new Promise((resolve, reject) => {
if (hasButton) {
Utils.toggleStatusButtons(id, true);
button.innerHTML = '<i class=\'fa fa-spinner fa-spin fa-1x fa-fw\'></i> Actualizando';
}
Client.modifyDomainByAttrs(domain.id, attrs, (success) => {
return resolve(success);
}, (error) => {
return reject(error);
});
}).then((res) => {
DomainStore.setCurrent(res);
this.setState({
update: true
});
}).catch((err) => {
console.log(err);
}).finally(() => {
if (hasButton) {
Utils.toggleStatusButtons(id, false);
button.innerHTML = oldContent;
}
if (hasInput) {
input.value = '';
}
});
}
render() {
let whiteList = null;
let blackList = null;
if (this.blackList.length > 0) {
blackList = this.blackList.map((black, i) => {
return (
<tr
key={`black-${i}`}
>
<td>
{black}
<Button
btnAttrs={
{
className: 'pull-right',
onClick: (e) => {
this.handleDelete(e, black, 'black');
}
}
}
>
<i className='fa fa-minus-circle text-danger'></i>
</Button>
</td>
</tr>
);
});
} else {
blackList = (
<tr>
<td className='text-center'>
<strong>No hay resultados para Lista Negra</strong>
</td>
</tr>
);
}
if (this.whiteList.length > 0) {
whiteList = this.whiteList.map((white, i) => {
return (
<tr
key={`white-${i}`}
>
<td>
{white}
<Button
btnAttrs={
{
className: 'pull-right',
onClick: (e) => {
this.handleDelete(e, white, 'white');
}
}
}
>
<i className='fa fa-minus-circle text-danger'></i>
</Button>
</td>
</tr>
);
});
} else {
whiteList = (
<tr>
<td className='text-center'>
<strong>No hay resultados para Lista blanca</strong>
</td>
</tr>
);
}
return (
<div>
<div className='row'>
<div className='col-xs-6 clearfix'>
<div className='row'>
<form className='form-inline'>
<div className='col-xs-12 text-right'>
<div className='input-group'>
<input
type='text'
className='form-control'
placeholder='Lista blanca'
ref='white-item'
/>
<span className='input-group-btn'>
<button
className='btn btn-info'
type='button'
onClick={(e) => {
this.handleSave(e, 'white');
}}
>
Agregar Lista Blanca
</button>
</span>
</div>
</div>
</form>
</div>
<div className='col-xs-12'>
<div className='row'>
<table className='table table-striped table-bordered table-hover dataTable no-footer'>
<thead>
<tr>
<th>
{'Nombre'}
<span className='pull-right'>
<i
className='glyphicon pull-right pointer'
>
</i>
</span>
</th>
</tr>
</thead>
<tbody>
{whiteList}
</tbody>
</table>
</div>
</div>
</div>
<div className='col-xs-6 clearfix'>
<div className='row'>
<form className='form-inline'>
<div className='col-xs-12 text-right'>
<div className='input-group'>
<input
type='text'
className='form-control'
placeholder='Lista Negra'
ref='black-item'
/>
<span className='input-group-btn'>
<button
className='btn btn-info'
type='button'
onClick={(e) => {
this.handleSave(e, 'black');
}}
>
Agregar Lista Negra
</button>
</span>
</div>
</div>
</form>
</div>
<div className='col-xs-12'>
<div className='row'>
<table className='table table-striped table-bordered table-hover dataTable no-footer'>
<thead>
<tr>
<th>
{'Nombre'}
<span className='pull-right'>
<i
className='glyphicon pull-right pointer'
>
</i>
</span>
</th>
</tr>
</thead>
<tbody>
{blackList}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
}
}
AntiSpam.propTypes = {
data: React.PropTypes.object.isRequired
};
......@@ -8,10 +8,14 @@ import MessageBar from '../message_bar.jsx';
import PageInfo from '../page_info.jsx';
import PanelTab from '../panel_tab.jsx';
import Button from '../button.jsx';
import DomainGeneralInfo from './domain_general_info.jsx';
import DomainMailboxPlans from './domain_mailbox_plans.jsx';
import DomainAdmins from './domain_admin_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';
......@@ -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 = (
<PanelTab
tabNames={['Administradores', 'Listas De Distribución']}
tabNames={['Administradores', 'AntiSpam', 'Listas De Distribución', 'Tareas Masivas']}
tabs={{
administradores: tabAdmin,
listas_de_distribución: tabDistribution
antispam: tabAntiSpam,
listas_de_distribución: tabDistribution,
tareas_masivas: tabTareasMasivas
}}
location={this.props.location}
/>
......@@ -113,6 +147,13 @@ export default class DomainDetails extends React.Component {
{message}
<div className='content animate-panel'>
<div className='row'>
<div className='layout-back clearfix'>
<div className='back-left backstage'>
<div className='backbg'></div>
</div>
<div className='back-right backstage'>
<div className='backbg'></div>
</div>
<div className='col-md-6 central-content'>
<DomainGeneralInfo
domain={domain}
......@@ -128,6 +169,7 @@ export default class DomainDetails extends React.Component {
/>
</div>
</div>
</div>
<div className='row'>
<div className='col-md-12 panel-with-tabs'>
{panelTabs}
......
......@@ -23,6 +23,7 @@ export default class DomainDistributionList extends React.Component {
}
getStateFromStores() {
const lists = DomainStore.getDistributionLists(this.props.domain);
return {
lists
};
......@@ -107,7 +108,7 @@ export default class DomainDistributionList extends React.Component {
<td className='distribution-list-actions'>
<a
href='#'
onClick={(e) => Utils.handleLink(e, `/distribution_lists/${dl.id}`)}
onClick={(e) => Utils.handleLink(e, `/domains/${domain.id}/distribution_lists/${dl.id}`)}
>
{'Ver'}
</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
};
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {Modal} from 'react-bootstrap';
import * as GlobalActions from '../action_creators/global_actions.jsx';
import SelectCols from './select-col.jsx';
import ZimbraStore from '../stores/zimbra_store.jsx';
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import MailboxStore from '../stores/mailbox_store.jsx';
export default class ImportMassiveModal extends React.Component {
constructor(props) {
super(props);
this.buildCols = this.buildCols.bind(this);
this.options = {
email: 'email',
contraseña: 'passwd',
plan: 'zimbraCOSId',
nombres: 'givenName',
apellidos: 'sn'
};
this.mandatory = {
email: true,
passwd: true,
zimbraCOSId: true,
total: 3
};
this.uploaded = null;
this.disabled = {};
this.plans = Utils.getEnabledPlansByCos(ZimbraStore.getAllCos());
this.state = {
options: this.options
};
}
onFileUploaded(e) {
e.preventDefault();
const file = e.target.files[0];
const isCSV = !(file.type === 'text/csv');
this.refs.console.value = this.refs.realfile.value;
if (isCSV) {
this.setState({
alert: true,
alertMessage: 'Solo se permite archivos de tipo CSV, verifique por favor',
cols: null
});
return false;
}
if (file) {
const fileReader = new FileReader();
fileReader.onload = (res) => {
const result = res.target.result;
if (result) {
const cols = this.extractCols(result, 'email');
if (cols.hasError) {
this.setState({
alert: true,
alertMessage: cols.message,
cols: null
});
return false;
}
const columns = cols.cols;
this.uploaded = columns;
this.setState({
cols: columns,
alert: null
});
Utils.toggleStatusButtons('.action-massive', false);
} else {
return this.setState({
alert: true,
alertMessage: 'Su archvio esta vacio, verifiquelo, por favor',
});
}
};
fileReader.readAsText(file);
}
}
onChangeColumn(e, option, key) {
const parent = e.target.parentNode;
if (option.restart) {
parent.classList.remove('ok-option');
parent.classList.add('missing-option');
Reflect.deleteProperty(this.disabled, option.restart);
}
if (option && !option.restart) {
parent.classList.remove('missing-option');
parent.classList.add('ok-option');
this.disabled[option] = {
col: key
};
}
this.setState({
change: true
});
}
buildCols(cols) {
const columns = Object.keys(cols).reverse().sort((a, b) => {
return a - b;
});
const obj = {};
columns.forEach((key) => {
if (cols.hasOwnProperty(key)) {
obj[key] = cols[key];
}
});
return {
columns,
obj
};
}
extractCols(data, flagDefault) {
let hasError = false;
const dataArray = data.split('\r\n');
const isEmpty = dataArray.some((pos) => {
if (pos === '') {
return true;
}
});
if (isEmpty) {
hasError = true;
return {
hasError,
message: 'Su archivo no es correcto, verifiquelo por favor.'
};
}
const limit = dataArray.length;
const isEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const error = [];
let message = null;
if (limit < 1) {
hasError = true;
message = 'Su archivo esta vacio, verifiquelo por favor';
return {
hasError,
error,
message
};
}
const array = [];
for (let k = 0; k < limit; k++) {
let row = dataArray[k];
row = row.replace(/\'|\"/g, '');
row = row.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
array.push(row);
}
const ul = {};
const total = array.length;
let lastRow = null;
for (let r = 0; r < total; r++) {
const totalCols = array[r].length;
if (lastRow) {
if (lastRow !== totalCols) {
hasError = true;
message = 'Su archivo presenta un error, verifique por favor.';
break;
}
}
lastRow = totalCols;
for (let c = 0; c < totalCols; c++) {
const col = array[r][c];
if (!ul[c]) {
if (col.indexOf('@') > -1) {
if (isEmail.test(col)) {
ul[c] = {}
ul[c][flagDefault] = [];
this.disabled[flagDefault] = {
col: c
};
} else {
error.push(col);
hasError = true;
}
} else {
ul[c] = [];
}
}
if (ul[c][flagDefault]) {
ul[c][flagDefault].push(col);
} else {
ul[c].push(col);
}
}
}
return {
hasError,
error,
message,
cols: ul
};
}
onSubmit() {
const padre = {};
let counter = 0;
for (const key in this.disabled) {
if (this.disabled.hasOwnProperty(key) && (this.disabled[key].col in this.uploaded)) {
const current = this.disabled[key].col;
if (this.mandatory[this.options[key]]) {
counter++;
}
const length = (Array.isArray(this.uploaded[current])) ? this.uploaded[current].length : this.uploaded[current]['email'].length;
const data = (Array.isArray(this.uploaded[current])) ? this.uploaded[current] : this.uploaded[current]['email'];
for (var i = 0; i < length; i++) {
if (!padre[i]) {
padre[i] = {};
}
padre[i][this.options[key]] = data[i];
}
}
}
if (counter === this.mandatory.total) {
this.setState({
alert: false
});
return this.createMassiveAccounts(padre);
}
return this.setState({
alert: true,
alertMessage: 'Faltan columnas que son obligatorias, verifique por favor.'
});
}
createMassiveAccounts(accounts) {
const allAccounts = [];
let hasError = false;
for (const account in accounts) {
if (accounts.hasOwnProperty(account)) {
accounts[account].zimbraCOSId = this.plans[accounts[account].zimbraCOSId.toLowerCase()];
const email = accounts[account].email;
const passwd = accounts[account].passwd;
if (passwd.length < 8) {
hasError = true;
break;
}
Reflect.deleteProperty(accounts[account], 'email');
Reflect.deleteProperty(accounts[account], 'passwd');
allAccounts.push(Client.createAccountByBatch(email, passwd, accounts[account]));
}
}
if (hasError) {
return this.setState({
alert: true,
alertMessage: 'La contraseña debe ser mayor a 8 caracteres, verifique su archivo por favor.'
});
}
GlobalActions.emitStartTask({
origin: 'Casillas',
action: 'Importando casillas masivamente.',
id: 'casillamasiva'
});
Client.batchRequest(allAccounts, (response) => {
if (response.CreateAccountResponse) {
if (Array.isArray(response.CreateAccountResponse)) {
const length = response.CreateAccountResponse.length;
for (let i = 0; i < length; i++) {
const account = response.CreateAccountResponse[i].account[0];
const accountFormatted = Client.buildAccountByObject(account);
MailboxStore.setMailbox(accountFormatted);
}
MailboxStore.emitAddMassive();
}
}
//Aqui va error batchrequest
GlobalActions.emitEndTask({
id: 'casillamasiva'
});
});
}
render() {
let ul = null;
let labelError = null;
if (this.state.alert) {
labelError = (
<label className='text-danger'>{this.state.alertMessage}</label>
);
}
if (this.state.cols) {
const columns = this.state.cols;
ul = [];
for (const col in columns) {
if (columns.hasOwnProperty(col)) {
const key = (Array.isArray(columns[col])) ? col : 'email';
const element = (Array.isArray(columns[col])) ? columns[col] : columns[col].email;
const selected = (Array.isArray(columns[col])) ? false : 'email';
const current = (selected) ? 'ok-option' : 'missing-option';
ul.push(
<div
className='col-import'
key={`wrap-col-${col}`}
>
<div className={`wrap-border-col ${current}`}>
<SelectCols
options={this.state.options}
selectAttrs={{
className: 'form-control'
}}
id={key}
onSelected={(e, option, id) => {
this.onChangeColumn(e, option, id);
}}
disabledOptions={this.disabled}
selected={selected}
/>
<ul className='list-unstyled list-attr'>
{element.map((item, i) => {
return (
<li
key={`option-${i}`}
title={item}
>
{item}
</li>
);
})}
</ul>
</div>
</div>
);
}
}
}
return (
<Modal
show={this.props.show}
onHide={this.props.onHide}
bsSize={'lg'}
onEnter={() => {
Utils.toggleStatusButtons('.action-massive', true);
}}
onExited={() => {
this.setState({
alert: null,
cols: null
});
this.disabled = {};
this.uploaded = null;
}}
>
<div className='color-line'></div>
<Modal.Header closeButton={true}>
<Modal.Title>
{'Importar Casillas'}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
<form ref='form-change-passwd'>
<div className='row'>
<div className='col-xs-12 text-center'>
<div className='input-group'>
<label
className='input-group-addon pointer'
htmlFor='importer'
>
<input
type='file'
className='hide'
ref='realfile'
id='importer'
onChange={(e) => {
this.onFileUploaded(e);
}}
/>
<i className='glyphicon glyphicon-hdd'></i>
{' Importar Archivo'}
</label>
<input
type='text'
className='form-control'
readOnly='readOnly'
ref='console'
/>
</div>
</div>
</div>
</form>
<div className='row'>
<div className='col-xs-12'>
<div className='wrapper-importer clearfix'>
{ul}
</div>
</div>
</div>
<div className='row text-center'>
{labelError}
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
className='btn btn-info action-massive'
type='button'
onClick={() => {
this.onSubmit();
}}
>
<i className='glyphicon glyphicon-open-file'></i>
{' Cargar Archivo'}
</button>
</Modal.Footer>
</Modal>
);
}
}
ImportMassiveModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired
};
......@@ -5,6 +5,7 @@ import LoadingScreen from './loading_screen.jsx';
import Header from './header.jsx';
import Sidebar from './sidebar.jsx';
import ToastAlert from './toast_alert.jsx';
import ProgressTask from './progress_task.jsx';
import React from 'react';
......@@ -12,6 +13,7 @@ export default class LoggedIn extends React.Component {
render() {
return (
<div>
<ProgressTask/>
<ToastAlert/>
<LoadingScreen/>
<Header/>
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {browserHistory} from 'react-router';
import {Modal} from 'react-bootstrap';
import UserStore from '../../stores/user_store.jsx';
import React from 'react';
import PasswordStrengthMeter from 'react-password-strength-meter';
export default class ConfirmDeleteModal extends React.Component {
constructor(props) {
......@@ -13,10 +13,24 @@ export default class ConfirmDeleteModal extends React.Component {
this.handleChangePasswd = this.handleChangePasswd.bind(this);
this.forceLogout = this.forceLogout.bind(this);
this.handlePasswd = this.handlePasswd.bind(this);
this.restart = this.restart.bind(this);
this.state = this.getStateFromStores();
}
restart() {
this.setState({
message: null
});
}
handlePasswd(e) {
const hidePasswd = this.refs.passwdfield;
hidePasswd.value = e.target.value;
}
getStateFromStores() {
const currentUser = UserStore.getCurrentUser();
return {currentUser};
......@@ -30,13 +44,24 @@ export default class ConfirmDeleteModal extends React.Component {
handleChangePasswd() {
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.setState({
alert: true,
message: 'Su contraseña se ha sido cambiada éxitosamente.',
typeError: 'text-success'
});
if (this.props.data.name === this.state.currentUser.name) {
this.forceLogout('/logout');
}
}, (error) => {
this.setState({
alert: true,
......@@ -66,6 +91,9 @@ export default class ConfirmDeleteModal extends React.Component {
<Modal
show={this.props.show}
onHide={this.props.onHide}
onExit={() => {
this.restart();
}}
>
<div className='color-line'></div>
<Modal.Header closeButton={true}>
......@@ -82,11 +110,26 @@ export default class ConfirmDeleteModal extends React.Component {
</div>
<div className='col-xs-7'>
<input
type='password'
type='hidden'
ref='passwdfield'
className='form-control'
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 className='col-xs-12 text-center'>
<br/>
......
......@@ -5,6 +5,9 @@ import {browserHistory} from 'react-router';
import {Modal} from 'react-bootstrap';
import * as Utils from './../../utils/utils.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';
......@@ -30,31 +33,45 @@ export default class ConfirmDeleteModal extends React.Component {
}
handleDelete() {
new Promise((resolve, reject) => {
// start loading
GlobalActions.emitStartLoading();
Client.removeAccount(
this.props.data.id,
() => {
return resolve(true);
},
(error) => {
return reject(error);
}
);
}).then(() => {
MailboxStore.removeAccount(this.props.data);
this.setState(
{
alert: true,
message: `Su cuenta ${this.props.data.attrs.mail}, ha sido eliminada éxitosamente. Sera redirigido en ${this.state.timeToRedirect} seg`,
typeError: 'text-success'
typeError: 'text-warning'
}
);
Utils.toggleStatusButtons('.close-modal', true);
this.redirect();
},
() => {
}).catch((error) => {
this.setState(
{
alert: true,
message: `Hubo un error, al intentar eliminar su cuenta : ${this.props.data.attrs.mail}`,
typeError: 'text-danger'
}
);
message: error.message,
typeError: 'text-edanger'
}
);
}).finally(() => {
GlobalActions.emitEndLoading();
});
}
redirect() {
const redirectAt = 1000;
setTimeout(() => {
if (this.timetoRedict-- < 1) {
browserHistory.replace('/mailboxes');
......@@ -65,7 +82,7 @@ export default class ConfirmDeleteModal extends React.Component {
this.redirect();
}
}, 1000);
}, redirectAt);
}
handleKeyUp() {
......@@ -120,7 +137,7 @@ export default class ConfirmDeleteModal extends React.Component {
<Modal.Footer>
<button
type='button'
className='btn btn-default'
className='btn btn-default close-modal'
onClick={this.props.onHide}
>
{'Cancelar'}
......
import $ from 'jquery';
import React from 'react';
import PasswordStrengthMeter from 'react-password-strength-meter';
import Panel from '../panel.jsx';
import Button from '../button.jsx';
import MessageBar from '../message_bar.jsx';
import Promise from 'bluebird';
import DataList from 'react-datalist';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import EventStore from '../../stores/event_store.jsx';
import MailboxStore from '../../stores/mailbox_store.jsx';
import DomainStore from '../../stores/domain_store.jsx';
import Constants from '../../utils/constants.jsx';
......@@ -17,73 +23,209 @@ export default class CreateMailBox extends React.Component {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.getAllDomains = this.getAllDomains.bind(this);
this.showMessage = this.showMessage.bind(this);
this.handleRadioChanged = this.handleRadioChanged.bind(this);
this.controllerDataList = this.controllerDataList.bind(this);
this.handlePasswd = this.handlePasswd.bind(this);
this.reset = null;
this.state = {};
}
handlePasswd(e) {
const hidePasswd = this.refs.passwd;
hidePasswd.value = e.target.value;
}
showMessage(attrs) {
this.setState({
error: attrs.message,
typeError: attrs.typeError
});
}
handleSubmit(e) {
e.preventDefault();
Utils.validateInputRequired(this.refs).then(() => {
// here assign missing properties.
let attrs = {
const domain = document.querySelector('input[list=\'domain\']');
const passwd = this.refs.passwd.value;
const maskPasswd = document.getElementById('password');
if (domain.value === '') {
GlobalActions.emitMessage({
message: 'El dominio es requerido, verifique por favor.',
typeError: messageType.ERROR
});
domain.focus();
return false;
}
if (passwd.length < 9) {
GlobalActions.emitMessage({
message: 'La contraseña debe ser mayor a 8 caracteres, verifique por favor.',
typeError: messageType.ERROR
});
maskPasswd.focus();
return false;
}
const email = this.refs.mail.value + '@' + domain.value;
//falta campo de tipo de casilla
const attrs = {
givenName: this.refs.givenName.value,
sn: this.refs.sn.value
sn: this.refs.sn.value,
description: this.refs.description.value,
zimbraCOSId: this.refs.zimbraCOSId.value,
zimbraAccountStatus: this.refs.zimbraAccountStatus.value
};
Client.createAccount(
this.refs.mail.value,
this.refs.mail.passwd,
email,
passwd,
attrs,
(data) => {
// reset form when all is ok
document.getElementById('createAccount').reset();
this.setState(
{
error: `Su cuenta ${data.name} ha sido creada con èxito.`,
this.reset.toggleOptions();
MailboxStore.setMailbox(data);
//return Utils.handleLink(event, `/mailboxes/${data.id}`, this.props.location);
GlobalActions.emitMessage({
message: 'Se ha creado su cuenta con éxito.',
typeError: messageType.SUCCESS
}
);
});
},
(error) => {
this.setState(
{
error: error.message,
typeError: messageType.WARNING
}
);
GlobalActions.emitMessage({
message: error.message,
typeError: messageType.ERROR
});
}
);
return true;
}).catch((err) => {
this.setState(
GlobalActions.emitMessage({
message: err.message,
typeError: messageType.ERROR
});
err.node.focus();
});
}
handleRadioChanged(val) {
this.refs.zimbraCOSId.value = val;
}
getAllDomains() {
const promises = [];
const max = 200;
const doms = new Promise((resolve, reject) => {
Client.getAllDomains(
{
error: err.message,
typeError: err.typeError
limit: max
},
(data) => {
resolve(data);
},
(error) => {
reject(error);
}
);
});
err.node.focus();
const cos = new Promise((resolve, reject) => {
Client.getAllCos((success) => {
resolve(success);
}, (error) => {
reject(error);
});
});
promises.push(doms, cos);
Promise.all(promises).then((success) => {
const response = {};
success.map((pos) => {
if (Array.isArray(pos)) {
const arrPlans = Utils.getEnabledPlansByCos(pos);
response.plans = arrPlans;
} else {
response.domains = pos;
}
return true;
});
componentDidMount() {
/*$('#selectDomains').select2({
placeholder: 'Por favor, introduzca 3 caracteres.',
allowClear: true
});*/
if (this.props.params.id) {
let defaultDomain = null;
if (DomainStore.getCurrent()) {
defaultDomain = DomainStore.getCurrent();
}
if (!defaultDomain) {
const currentDomainId = this.props.params.id;
defaultDomain = response.domains.domain.find((domain) => {
if (currentDomainId === domain.id) {
return domain;
}
});
}
$('#passwdMeter').pwstrength();
response.currentDomain = defaultDomain.name;
}
this.setState(response);
GlobalActions.emitEndLoading();
Utils.toggleStatusButtons('.action-save', false);
}, (error) => {
GlobalActions.emitMessage({
error: error.message,
typeError: error.type
});
GlobalActions.emitEndLoading();
Utils.toggleStatusButtons('.action-save', false);
});
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
Utils.toggleStatusButtons('.action-save', true);
$('#sidebar-mailboxes').addClass('active');
this.getAllDomains();
GlobalActions.emitEndLoading();
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
$('#sidebar-mailboxes').removeClass('active');
}
controllerDataList(controller) {
this.reset = controller;
}
render() {
let message;
let domains = [];
let form = null;
let checkboxes = [];
if (this.state.error) {
message = (
<MessageBar
......@@ -94,7 +236,40 @@ export default class CreateMailBox extends React.Component {
);
}
let form = (
if (this.state.domains) {
const object = this.state.domains.domain;
const plans = this.state.plans;
let limit = object.length;
for (; limit-- > 0;) {
domains.push(object[limit].name);
}
for (let plan in plans) {
if (plans.hasOwnProperty(plan)) {
const item = (
<label
className='radio radio-info radio-inline pretty-input'
key={plan}
>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
onChange={() => {
this.handleRadioChanged(plans[plan]);
}}
/>
<span></span>
</div>
{Utils.titleCase(plan)}
</label>
);
checkboxes.push(item);
}
}
form = (
<form
className='simple_form form-horizontal mailbox-form'
onSubmit={(e) => {
......@@ -110,23 +285,25 @@ export default class CreateMailBox extends React.Component {
<div className='col-sm-8'>
<div className='row'>
<div className='col-xs-6'>
<div className='col-xs-12'>
<div className='input-group'>
<input
type='text'
className='form-control'
ref='mail'
data-required='true'
data-message='El nombre de la casilla es requerida, verifique por favor.'
placeholder='Mail'
/>
</div>
<div className='col-xs-6'>
<div className='input-group'>
<span className='input-group-addon'>{'@'}</span>
<select
<DataList
list='domain'
options={domains}
className='form-control'
id='selectDomains'
ref='domain'
>
</select>
placeholder='Dominio'
getController={this.controllerDataList}
initialFilter={this.state.currentDomain}
/>
</div>
</div>
</div>
......@@ -176,21 +353,19 @@ export default class CreateMailBox extends React.Component {
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Administrador delegado'}
<label className='string col-sm-3 control-label'>
{'Status'}
</label>
<div className='col-sm-8'>
<label className='radio-inline pretty-input'>
<div className='pretty-checkbox'>
<input
type='checkbox'
className='pretty'
ref='zimbraIsDelegatedAdminAccount'
/>
<span></span>
</div>
</label>
<select
className='form-control'
ref='zimbraAccountStatus'
>
<option value='active'>Activa</option>
<option value='closed'>Cerrada</option>
<option value='locked'>Bloqueada</option>
</select>
</div>
</div>
......@@ -201,44 +376,15 @@ export default class CreateMailBox extends React.Component {
</label>
<div className='col-sm-8'>
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
ref='mailbox_basic'
/>
<span></span>
</div>
{'Básica'}
</label>
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
ref='mailbox_professional'
/>
<span></span>
</div>
{'Profesional'}
</label>
{checkboxes}
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
ref='mailbox_premium'
type='hidden'
ref='zimbraCOSId'
data-required='true'
data-message='El plan de su casilla es requerido, por favor verificar.'
/>
<span></span>
</div>
{'Premium'}
</label>
</div>
</div>
......@@ -250,12 +396,28 @@ export default class CreateMailBox extends React.Component {
<div className='col-sm-8'>
<input
type='password'
type='hidden'
className='form-control'
data-required='true'
data-message='La contraseña de su casilla es requerida, verifique por favor.'
ref='passwd'
id='passwdMeter'
/>
<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>
......@@ -265,7 +427,7 @@ export default class CreateMailBox extends React.Component {
type='submit'
name='commit'
value='Guardar'
className='btn btn-primary'
className='btn btn-primary action-save'
/>
<Button
btnAttrs={
......@@ -283,6 +445,7 @@ export default class CreateMailBox extends React.Component {
</div>
</form>
);
}
const actions = [
{
......@@ -298,8 +461,8 @@ export default class CreateMailBox extends React.Component {
return (
<div>
{message}
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-12 central-content'>
<Panel
......
......@@ -6,10 +6,15 @@ import MessageBar from '../message_bar.jsx';
import Panel from '../panel.jsx';
import ConfirmDeleteModal from './confirm_delete_modal.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import DataList from 'react-datalist';
import UserStore from '../../stores/user_store.jsx';
import Promise from 'bluebird';
import MailboxStore from '../../stores/mailbox_store.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 Constants from '../../utils/constants.jsx';
......@@ -23,12 +28,129 @@ export default class EditMailBox extends React.Component {
this.handleRadioChanged = this.handleRadioChanged.bind(this);
this.getMailbox = this.getMailbox.bind(this);
this.fillForm = this.fillForm.bind(this);
this.showMessage = this.showMessage.bind(this);
this.handleRenameAccount = this.handleRenameAccount.bind(this);
this.state = {};
}
showMessage(attrs) {
this.setState({
error: attrs.message,
typeError: attrs.typeError
});
}
handleRadioChanged(val) {
this.refs.plan.value = val;
this.refs.zimbraCOSId.value = val;
}
handleEnabledRename() {
const selfButton = this.refs.rename;
const inputs = document.querySelectorAll('.action-rename');
const email = inputs[0];
const domain = inputs[1];
if (selfButton.hasAttribute('data-rename')) {
selfButton.removeAttribute('data-rename');
selfButton.innerHTML = 'Actualizar';
Utils.toggleStatusButtons('.action-rename', false);
return false;
}
if (domain.value === '') {
GlobalActions.emitMessage({
message: 'El dominio es requerido, verifique por favor.',
typeError: messageType.ERROR
});
domain.focus();
return false;
}
if (email.value === '') {
GlobalActions.emitMessage({
message: 'El campo mail es requerido, verifique por favor.',
typeError: messageType.ERROR
});
email.focus();
return false;
}
const mail = email.value + '@' + domain.value;
const isEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!isEmail.test(mail)) {
GlobalActions.emitMessage({
message: 'El email no tiene el formato correcto, verifique por favor.',
typeError: messageType.ERROR
});
return false;
}
this.handleRenameAccount(mail);
}
handleRenameAccount(email) {
const account = MailboxStore.getCurrent();
if (account) {
const oldName = this.refs.rename.innerHTML;
this.refs.rename.innerHTML = 'Actualizando';
Utils.toggleStatusButtons('.change-email', true);
return account.rename(email, (error, success) => {
if (error) {
this.setState({
data: account
});
this.refs.rename.innerHTML = oldName;
Utils.toggleStatusButtons('.change-email', false);
return GlobalActions.emitMessage({
message: error.extra.reason,
typeError: messageType.ERROR
});
}
let newAccount = MailboxStore.changeAccount(success);
if (!newAccount) {
newAccount = success;
}
this.setState({
data: newAccount
});
this.refs.rename.innerHTML = 'Renombrar';
this.refs.rename.setAttribute('data-rename', 'true');
Utils.toggleStatusButtons('.change-email', false);
return GlobalActions.emitMessage({
message: 'Se ha modificado el nombre de su cuenta éxitosamente',
typeError: messageType.SUCCESS
});
});
}
GlobalActions.emitMessage({
message: 'Error, no existe instancia de la casilla.',
typeError: messageType.ERROR
});
}
componentDidUpdate() {
const button = this.refs.rename;
if (button.hasAttribute('data-rename')) {
Utils.toggleStatusButtons('.action-rename', true);
}
}
handleEdit(e) {
......@@ -36,41 +158,46 @@ export default class EditMailBox extends React.Component {
Utils.toggleStatusButtons('.action-button', true);
Utils.validateInputRequired(this.refs).then(() => {
let attrs = {
// fill new attrs
const attrs = {
givenName: this.refs.givenName.value,
sn: this.refs.sn.value
sn: this.refs.sn.value,
description: this.refs.description.value,
zimbraCOSId: this.refs.zimbraCOSId.value,
zimbraAccountStatus: this.refs.zimbraAccountStatus.value
};
GlobalActions.emitStartLoading();
return new Promise((resolve, reject) => {
Client.modifyAccount(
this.state.data.id,
this.props.params.id,
attrs,
(data) => {
Utils.toggleStatusButtons('.action-button', false);
this.setState(
{
error: `Su cuenta ${data.name} ha sido modificada con èxito.`,
typeError: messageType.SUCCESS,
data: data
}
);
(account) => {
return resolve(account);
},
(error) => {
this.setState(
{
error: error.message,
typeError: messageType.WARNING
return reject(error);
}
);
}).then((account) => {
MailboxStore.changeAccount(account);
Utils.handleLink(e, `/mailboxes/${this.props.params.id}`, this.props.location);
}).catch((error) => {
GlobalActions.emitMessage({
message: error.message,
typeError: messageType.ERROR
});
}).finally(() => {
GlobalActions.emitEndLoading();
Utils.toggleStatusButtons('.action-button', false);
}
);
});
}).catch((err) => {
this.setState(
{
error: err.message,
typeError: err.typeError
}
);
GlobalActions.emitMessage({
message: err.message,
typeError: messageType.ERROR
});
err.node.focus();
Utils.toggleStatusButtons('.action-button', false);
......@@ -78,43 +205,156 @@ export default class EditMailBox extends React.Component {
}
getMailbox(id) {
Client.getAccount(
id,
(data) => {
const promises = [];
let data = null;
const max = 200;
Utils.toggleStatusButtons('.action-save', true);
if (MailboxStore.hasMailboxes()) {
data = MailboxStore.getMailboxById(id);
const domains = new Promise((resolve, reject) => {
Client.getAllDomains(
{
limit: max
},
(dataDomains) => {
return resolve(dataDomains);
},
(error) => {
return reject(error);
}
);
});
const cos = new Promise((resolve, reject) => {
Client.getAllCos((success) => {
resolve(success);
}, (error) => {
reject(error);
});
});
promises.push(domains, cos);
Promise.all(promises).then((result) => {
MailboxStore.setCurrent(data);
this.setState({
data
data,
domains: result.shift().domain,
cos: Utils.getEnabledPlansByCos(result.shift())
});
Utils.toggleStatusButtons('.action-save', false);
}).catch((error) => {
GlobalActions.emitMessage({
message: error.message,
typeError: error.type
});
}).finally(() => {
GlobalActions.emitEndLoading();
});
} else {
const mailbox = new Promise((resolve, reject) => {
Client.getAccount(
id,
(resultMailbox) => {
return resolve(resultMailbox);
},
(error) => {
return reject(error);
}
);
});
const doms = new Promise((resolve, reject) => {
Client.getAllDomains(
{
limit: max
},
(domain) => {
return resolve(domain);
},
(error) => {
return reject(error);
}
);
});
const cos = new Promise((resolve, reject) => {
Client.getAllCos((success) => {
resolve(success);
}, (error) => {
reject(error);
});
});
promises.push(mailbox, doms, cos);
Promise.all(promises).then((result) => {
const account = result.shift();
this.setState({
error: error.message
data: account,
domains: result.shift().domain,
cos: Utils.getEnabledPlansByCos(result.shift())
});
Utils.toggleStatusButtons('.action-save', false);
MailboxStore.setCurrent(account);
}).catch((error) => {
GlobalActions.emitMessage({
message: error.message,
typeError: error.type
});
}).finally(() => {
GlobalActions.emitEndLoading();
});
}
);
}
componentDidMount() {
/*Client.renameAccount('nuevomodificado@zboxapp.dev', (exito) => {
console.log('exito', exito);
}, (fallo) => {
console.log('fallo', fallo);
});*/
$('#sidebar-mailboxes').addClass('active');
GlobalActions.emitEndLoading();
EventStore.addMessageListener(this.showMessage);
this.getMailbox(this.props.params.id);
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
$('#sidebar-mailboxes').removeClass('active');
}
fillForm() {
let attrs = this.state.data.attrs;
this.refs.mail.value = this.state.data.name;
fillForm(data) {
const attrs = data.attrs;
this.refs.mail.value = data.name.split('@').shift();
this.refs.givenName.value = attrs.givenName || '';
this.refs.sn.value = attrs.sn;
this.refs.description.value = attrs.description || '';
this.refs.zimbraCOSId.value = attrs.zimbraCOSId || '';
this.refs.zimbraAccountStatus.value = attrs.zimbraAccountStatus;
}
render() {
let message;
let data;
let actions;
let form;
const domains = [];
let buttonDelete = null;
let currentDomain = '';
const cosElements = [];
let datalist = (
<input
type='text'
className='form-control'
placeholder='Dominio'
/>
);
if (this.state.error) {
message = (
<MessageBar
......@@ -125,13 +365,78 @@ export default class EditMailBox extends React.Component {
);
}
let data;
let actions;
let form;
if (this.state.data) {
data = this.state.data;
this.fillForm();
const doms = this.state.domains;
const cos = this.state.cos;
currentDomain = data.name.split('@').pop();
buttonDelete = (
<ToggleModalButton
role='button'
className='btn btn-xs btn-danger action-button'
dialogType={ConfirmDeleteModal}
dialogProps={{data}}
key='delete-mailbox'
>
{'Eliminar'}
</ToggleModalButton>
);
const length = doms.length;
for (let i = 0; i < length; i++) {
domains.push(doms[i].name);
}
for (let cosName in cos) {
if (cos.hasOwnProperty(cosName)) {
let isChecked = false;
const id = data.attrs.zimbraCOSId;
if (id) {
if (cos[cosName] === id) {
isChecked = 'checked';
}
}
const checkbox = (
<label
key={cos[cosName]}
className='radio radio-info radio-inline pretty-input'
>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
defaultChecked={isChecked}
onChange={() => {
this.handleRadioChanged(cos[cosName]);
}}
/>
<span></span>
</div>
{cosName}
</label>
);
cosElements.push(checkbox);
}
}
if (UserStore.getCurrentUser().name === data.name) {
buttonDelete = null;
}
this.fillForm(data);
datalist = (
<DataList
list='domain'
options={domains}
className='form-control action-rename'
placeholder='Dominio'
initialFilter={currentDomain}
/>
);
}
form = (
......@@ -150,24 +455,29 @@ export default class EditMailBox extends React.Component {
<div className='col-sm-8'>
<div className='row'>
<div className='col-xs-6'>
<div className='col-xs-12'>
<div className='input-group'>
<input
type='text'
className='form-control'
className='form-control action-rename'
ref='mail'
data-required='true'
data-message='El campo direccion es requerido, verifique por favor.'
placeholder='Mail'
/>
</div>
<div className='col-xs-6'>
<div className='input-group'>
<span className='input-group-addon'>{'@'}</span>
<select
className='form-control'
id='selectDomains'
ref='domain'
{datalist}
<span className='input-group-btn'>
<button
className='btn btn-default change-email'
type='button'
ref='rename'
data-rename='true'
onClick={() => {
this.handleEnabledRename();
}}
>
</select>
Renombrar
</button>
</span>
</div>
</div>
</div>
......@@ -217,60 +527,34 @@ export default class EditMailBox extends React.Component {
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
<abbr title='Requerido'>{'*'}</abbr>
{'Tipo de casilla'}
<label className='string col-sm-3 control-label'>
{'Status'}
</label>
<div className='col-sm-8'>
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
onChange={() => {
this.handleRadioChanged('basica');
}}
/>
<span></span>
<select
className='form-control'
ref='zimbraAccountStatus'
>
<option value='active'>Activa</option>
<option value='closed'>Cerrada</option>
<option value='locked'>Bloqueada</option>
</select>
</div>
{'Básica'}
</label>
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
className='pretty'
name='mailbox'
type='radio'
onChange={() => {
this.handleRadioChanged('profesional');
}}
/>
<span></span>
</div>
{'Profesional'}
</label>
<label className='radio radio-info radio-inline pretty-input'>
<div className='pretty-radio'>
<input
type='radio'
className='pretty'
name='mailbox'
onChange={() => {
this.handleRadioChanged('premium');
}}
/>
<span></span>
</div>
{'Premium'}
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
<abbr title='Requerido'>{'*'}</abbr>
{'Tipo de casilla'}
</label>
<div className='col-sm-8'>
{cosElements}
<input
type='hidden'
ref='plan'
ref='zimbraCOSId'
data-required='true'
data-message='El plan de su cuenta es requerido, por favor verificar.'
/>
......@@ -309,29 +593,19 @@ export default class EditMailBox extends React.Component {
props: {
className: 'btn btn-default btn-xs action-button',
onClick: (e) => {
Utils.handleLink(e, '/mailboxes', this.props.location);
Utils.handleLink(e, `/mailboxes/${this.props.params.id}`, this.props.location);
}
}
},
{
setComponent: (
<ToggleModalButton
role='button'
className='btn btn-xs btn-danger action-button'
dialogType={ConfirmDeleteModal}
dialogProps={{data}}
key='delete-mailbox'
>
{'Eliminar'}
</ToggleModalButton>
)
setComponent: buttonDelete
}
];
return (
<div>
{message}
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-12 central-content'>
<Panel
......
import React from 'react';
import Datalist from 'react-datalist';
import Button from '../button.jsx';
import * as Utils from '../../utils/utils.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import Constants from '../../utils/constants.jsx';
const typeErrors = Constants.MessageType;
export default class FormAliasMailbox extends React.Component {
constructor(props) {
super(props);
this.handleDeleteAlias = this.handleDeleteAlias.bind(this);
this.sortAlias = this.sortAlias.bind(this);
this.addAlias = this.addAlias.bind(this);
this.toggleClass = 'glyphicon-sort-by-attributes';
this.asc = true;
this.state = {
alias: this.getAlias()
};
}
addAlias(e) {
e.preventDefault();
const refs = this.refs;
const newAlias = refs.alias.value + '@' + refs.domain.value;
Utils.validateInputRequired(refs).then(() => {
this.props.data.addAccountAlias(newAlias, () => {
GlobalActions.emitMessage({
message: 'Se ha agregado su nuevo Alias con éxito.',
type: typeErrors.SUCCESS
});
let alias = this.state.alias;
alias.push(newAlias);
this.setState({
alias: alias
});
}, (err) => {
GlobalActions.emitMessage({
message: err.error,
type: typeErrors.ERROR
});
});
}, (error) => {
GlobalActions.emitMessage({
message: error.message,
type: typeErrors.ERROR
});
error.node.focus();
});
}
sortAlias() {
let sort;
if (this.asc) {
sort = this.state.alias;
sort.sort().reverse();
this.setState({
alias: sort
});
this.asc = false;
this.toggleClass = 'glyphicon-sort-by-attributes';
} else {
sort = this.state.alias;
sort.sort();
this.setState({
alias: sort
});
this.asc = true;
this.toggleClass = 'glyphicon-sort-by-attributes-alt';
}
}
handleDeleteAlias(e, alias, index) {
e.preventDefault();
this.props.data.removeAccountAlias(alias, () => {
GlobalActions.emitMessage({
message: 'Se ha eliminado el alias éxitosamente.',
type: typeErrors.SUCCESS
});
const newAlias = Utils.removeIndexFromArray(this.state.alias, index);
this.setState({
alias: newAlias
});
}, (err) => {
GlobalActions.emitMessage({
message: err.error,
type: typeErrors.ERROR
});
});
}
getAlias() {
const data = this.props.data;
if (data.attrs.hasOwnProperty('zimbraMailAlias')) {
if (typeof data.attrs.zimbraMailAlias === 'string') {
return new Array(data.attrs.zimbraMailAlias);
}
return data.attrs.zimbraMailAlias.sort();
}
return [];
}
componentDidMount() {
let refs = this.refs;
refs.domain = refs.domain.refs.theInput;
refs.domain.setAttribute('data-required', 'true');
refs.domain.setAttribute('data-message', 'El dominio es necesario para crear el alias, verifiquelo por favor.');
}
render() {
let tbody;
let results;
// this bellow is just for testing porpuse
let options = ['apple', 'orange', 'pear', 'pineapple', 'melon'];
if (this.state.alias) {
tbody = this.state.alias.map((alias, i) => {
return (
<tr
key={`alias-${alias}-${i}`}
>
<td>
<span>{alias}</span>
<Button
btnAttrs={{
className: 'pull-right',
title: `Borrar el siguiente Alias : ${alias}`,
onClick: (e) => {
this.handleDeleteAlias(e, alias, i);
}
}}
>
<i className='fa fa-minus-circle text-danger'></i>
</Button>
</td>
</tr>
);
});
}
results = (
<table className='table table-striped table-bordered table-hover dataTable no-footer'>
<thead>
<tr>
<th>
{'Nombre'}
<span className='pull-right'>
<i
className={`glyphicon ${this.toggleClass} pull-right pointer`}
onClick={() => {
this.sortAlias();
}}
>
</i>
</span>
</th>
</tr>
</thead>
<tbody>
{tbody}
</tbody>
</table>
);
return (
<div className='row'>
<div className='col-xs-6'>
<div className='row'>
<form className='form-inline'>
<div className='col-xs-4'>
<div className='form-group'>
<label>
{'Mostrar'}
<select
ref='shown'
className='form-control input-sm'
>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
<option value='100'>100</option>
</select>
</label>
</div>
</div>
<div className='col-xs-8 text-right'>
<label>
{'Buscar'}
<input
type='search'
className='form-control input-sm'
ref='search'
/>
</label>
</div>
</form>
</div>
<div className='row'>
<div className='col-xs-12'>
{results}
</div>
</div>
<div className='row'>
<div className='col-xs-6'>
<div className='dataTables_info'>
1 al 2 de 2 resultados
</div>
</div>
<div className='col-xs-6 text-right'>
<div className='btn-group'>
<Button
btnAttrs={
{
className: 'btn btn-default'
}
}
>
{'Anterior'}
</Button>
<Button
btnAttrs={
{
className: 'btn btn-default'
}
}
>
{'Siguiente'}
</Button>
</div>
</div>
</div>
</div>
<div className='col-xs-6'>
<div className='input-group'>
<input
type='text'
ref='alias'
className='form-control'
placeholder='Alias'
data-required='true'
data-message='El alias es requerido, compruebelo por favor'
/>
<span className='input-group-addon'>
@
</span>
<Datalist
list='domains'
options={options}
className='form-control'
id='domain'
ref='domain'
placeholder='Dominio'
/>
<span className='input-group-btn'>
<Button
btnAttrs={
{
className: 'btn btn-default',
onClick: (e) => {
this.addAlias(e);
}
}
}
>
Agregar alias
</Button>
</span>
</div>
</div>
</div>
);
}
}
FormAliasMailbox.propTypes = {
data: React.PropTypes.object
};
......@@ -30,12 +30,12 @@ export default class FormVacacionesMailbox extends React.Component {
Client.modifyAccount(data.id, attrs, () => {
GlobalActions.emitMessage({
message: 'Se ha modificado con éxito',
error: 'Se ha modificado su respuesta de vacaciones con éxito.',
type: messageType.SUCCESS
});
}, (error) => {
GlobalActions.emitMessage({
message: error.message,
error: error.message,
type: messageType.ERROR
});
});
......
import React from 'react';
import Button from '../button.jsx';
import StatusLabel from '../status_label.jsx';
import * as Client from '../../utils/client.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 {
constructor(props) {
......@@ -16,24 +19,22 @@ export default class BlockGeneralInfoMailbox extends React.Component {
this.state = {};
}
componentWillMount() {
this.domain = this.props.data.name.split('@');
this.domain = this.domain[this.domain.length - 1];
}
componentDidMount() {
this.getDomain();
}
getDomain() {
Client.getDomain(this.domain, (data) => {
const domain = Utils.getDomainFromString(this.data.name);
Client.getDomain(domain, (data) => {
this.setState({
hasDomain: true,
domainData: data
});
}, (error) => {
this.setState({
error: error.message
GlobalActions.emitMessage({
error: error.message,
typeError: error.type
});
});
}
......@@ -44,25 +45,64 @@ export default class BlockGeneralInfoMailbox extends React.Component {
render() {
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) {
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 = (
<article>
<article className='account-info'>
<div>
<h4>
<span className='mailbox-name text-success'>{this.data.name}</span>
<span className='mailbox-name text-success'>{mail}</span>
</h4>
{owner && (
<h5>
{owner}
</h5>
)}
</div>
{description && (
<div>
<p>
{this.data.attrs.description}
{description}
</p>
</div>
)}
<div>
<p>
<strong>{'Dominio'}: </strong>
<strong>{'Dominio: '}</strong>
<Button
btnAttrs={{
onClick: (e) => {
......@@ -70,10 +110,36 @@ export default class BlockGeneralInfoMailbox extends React.Component {
}
}}
>
{this.domain}
{this.state.domainData.name}
</Button>
</p>
</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>
);
}
......
......@@ -3,44 +3,154 @@
import $ from 'jquery';
import React from 'react';
import {browserHistory} from 'react-router';
import Promise from 'bluebird';
import EventStore from '../../stores/event_store.jsx';
import Button from '../button.jsx';
import MessageBar from '../message_bar.jsx';
import PageInfo from '../page_info.jsx';
import Panel from '../panel.jsx';
import PanelTab from '../panel_tab.jsx';
import Pagination from '../pagination.jsx';
import MailboxStore from '../../stores/mailbox_store.jsx';
import Button from '../button.jsx';
import statusLabel from '../status_label.jsx';
import MessageBar from '../message_bar.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 Constants from '../../utils/constants.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import ImportMassiveModal from '../import_massive_modal.jsx';
import DomainStore from '../../stores/domain_store.jsx';
const QueryOptions = Constants.QueryOptions;
const messageType = Constants.MessageType;
import ZimbraStore from '../../stores/zimbra_store.jsx';
export default class Mailboxes extends React.Component {
constructor(props) {
super(props);
this.handleEdit = this.handleEdit.bind(this);
this.getMailboxes = this.getMailboxes.bind(this);
this.handleAddMailbox = this.handleAddMailbox.bind(this);
this.handleExportAsCSV = this.handleExportAsCSV.bind(this);
this.handleTabChanged = this.handleTabChanged.bind(this);
this.handleWatchInfo = this.handleWatchInfo.bind(this);
this.domainInfo = this.domainInfo.bind(this);
this.showMessage = this.showMessage.bind(this);
this.refreshAllAccounts = this.refreshAllAccounts.bind(this);
this.handleFilterMailbox = this.handleFilterMailbox.bind(this);
this.handleChangeFilter = this.handleChangeFilter.bind(this);
const page = parseInt(this.props.location.query.page, 10) || 1;
this.mailboxes = null;
this.status = '';
this.cos = null;
this.state = {
page,
offset: ((page - 1) * QueryOptions.DEFAULT_LIMIT)
};
}
handleChangeFilter(e) {
const selected = e.target.value;
const cos = Utils.getEnabledPlansByCos(ZimbraStore.getAllCos());
if (e.target.className.indexOf('plans') > -1) {
this.cos = cos[selected];
}
if (e.target.className.indexOf('status') > -1) {
this.status = selected;
}
const data = Object.assign({}, this.mailboxes);
const arrayFiltered = data.account.filter((strArray) => {
const status = this.status === '' ? strArray.attrs.zimbraAccountStatus : this.status;
const plan = this.cos ? this.cos : strArray.attrs.zimbraCOSId;
if (strArray.attrs.zimbraAccountStatus === status && strArray.attrs.zimbraCOSId === plan) {
return strArray;
}
return false;
});
data.account = arrayFiltered;
data.total = arrayFiltered.length;
const tables = this.buildTableFromData(data, ['Todas', 'Bloqueadas']);
return this.setState({
data: tables
});
}
handleFilterMailbox(e, info) {
const search = e.target.value;
const data = Object.assign({}, info);
const arrayFiltered = data.account.filter((strArray) => {
if (this.status === '') {
if (strArray.name.match(search)) {
return strArray;
}
}
if (strArray.name.match(search) && strArray.attrs.zimbraAccountStatus === this.status) {
return strArray;
}
return false;
});
data.account = arrayFiltered;
data.total = arrayFiltered.length;
const tables = this.buildTableFromData(data, ['Todas', 'Bloqueadas']);
return this.setState({
data: tables
});
}
showMessage(attrs) {
this.setState({
error: attrs.error,
type: attrs.typeError
});
}
handleExportAsCSV(e) {
e.preventDefault();
const accounts = MailboxStore.getMailboxes();
if (accounts && accounts.account && accounts.account.length > 0) {
return Utils.exportAsCSV(accounts.account, 'all_accounts', true);
}
return false;
}
componentWillReceiveProps(newProps) {
const condition = this.props.location.query.page !== newProps.location.query.page;
if (condition) {
const page = parseInt(newProps.location.query.page, 10) || 1;
GlobalActions.emitStartLoading();
this.state = {
page,
offset: ((page - 1) * QueryOptions.DEFAULT_LIMIT)
};
this.getAllMailboxes();
} else {
GlobalActions.emitStartLoading();
let domainId;
if (newProps.params.domain_id !== this.props.params.domain_id) {
domainId = newProps.params.domain_id;
}
this.getAllMailboxes(domainId);
}
}
domainInfo(domainId) {
......@@ -65,131 +175,102 @@ export default class Mailboxes extends React.Component {
);
}
handleWatchInfo(e, path, location) {
Utils.handleLink(e, path, location);
getAccounts(domainName) {
const attrs = {};
if (domainName) {
attrs.domain = domainName;
}
getAccounts(domainName) {
Client.getAllAccounts(
{
limit: QueryOptions.DEFAULT_LIMIT,
offset: this.state.offset,
new Promise((resolve, reject) => {
if (domainName) {
return Client.getAllAccounts(attrs, (success) => {
return resolve(success);
}, (error) => {
return reject(error);
});
}
if (MailboxStore.hasMailboxes()) {
resolve(MailboxStore.getMailboxes());
}
return Client.getAllAccounts(attrs, (success) => {
MailboxStore.setMailboxes(success);
this.mailboxes = this.mailboxes = MailboxStore.getMailboxes();
return resolve(success);
}, (error) => {
return reject(error);
});
}).then((data) => {
if (data.account) {
const tables = this.buildTableFromData(data, ['Todas', 'Bloqueadas']);
if (tables.lockedAlert) {
GlobalActions.emitMessage({
error: tables.lockedAlert.message,
typeError: messageType.LOCKED
});
}
return this.setState({
data: tables
});
}
return this.setState({
notMatches: true,
domain: domainName
},
(data) => {
this.setState({
data
});
}).catch(() => {
console.log('error',error);
}).finally(() => {
GlobalActions.emitEndLoading();
},
(error) => {
this.setState({
error: error.message
});
GlobalActions.emitEndLoading();
}
);
}
getMailboxes() {
const domainId = this.props.params.domain_id;
getAllMailboxes(domainId) {
if (domainId) {
return this.domainInfo(domainId).then(
() => {
return this.domainInfo(domainId).then(() => {
const domain = DomainStore.getCurrent();
this.getAccounts(domain.name);
}
);
});
}
return this.getAccounts();
}
handleAddMailbox(e, path) {
e.preventDefault();
if ((this.props.location.basename + this.props.location.pathname) !== path) {
browserHistory.push(path);
GlobalActions.emitStartLoading();
}
}
handleExportAsCSV(e) {
e.preventDefault();
}
refreshAllAccounts() {
const mailboxes = MailboxStore.getMailboxes();
const tables = this.buildTableFromData(mailboxes, ['Todas', 'Bloqueadas']);
handleTabChanged(tab) {
browserHistory.push(this.props.location.pathname + '?tab=' + tab);
if (tables.lockedAlert) {
GlobalActions.emitMessage({
error: tables.lockedAlert.message,
typeError: messageType.LOCKED
});
}
componentWillReceiveProps(newProps) {
if (this.props.location.query.page !== newProps.location.query.page) {
const page = parseInt(newProps.location.query.page, 10) || 1;
GlobalActions.emitStartLoading();
this.state = {
page,
offset: ((page - 1) * QueryOptions.DEFAULT_LIMIT)
};
this.getMailboxes();
}
this.setState({
data: tables
});
}
componentDidMount() {
$('#sidebar-mailboxes').addClass('active');
this.getMailboxes();
EventStore.addMessageListener(this.showMessage);
MailboxStore.addListenerAddMassive(this.refreshAllAccounts);
const domainId = this.props.params.domain_id;
this.getAllMailboxes(domainId);
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
MailboxStore.removeListenerAddMassive(this.showMessage);
$('#sidebar-mailboxes').removeClass('active');
}
handleEdit(e, path) {
e.preventDefault();
if ((this.props.location.basename + this.props.location.pathname) !== path) {
GlobalActions.emitStartLoading();
browserHistory.push(path);
}
}
render() {
let message;
let panelTabs;
if (this.state.error) {
message = (
<MessageBar
message={this.state.error}
type={messageType.SUCCESS}
autoclose={true}
/>
);
}
let tableResults;
let total = 0;
const arrLocked = [];
if (this.state.data) {
const accounts = this.state.data.account || [];
total = accounts.length;
tableResults = accounts.map((mail) => {
let attrs = mail.attrs;
let statusClass = '';
let status = attrs.zimbraAccountStatus;
let id = mail.id;
switch (status) {
case 'locked':
statusClass = 'label label-warning';
status = 'Bloqueada';
arrLocked.push(mail);
break;
default:
statusClass = 'label label-success';
status = 'Activa';
break;
}
buildRow(row, classes, status) {
const id = row.id;
return (
<tr
key={id}
......@@ -197,25 +278,25 @@ export default class Mailboxes extends React.Component {
id={id}
>
<td className={'mailbox-name'}>
<statusLabel className={statusClass}>{status}</statusLabel>
<statusLabel className={classes}>{status}</statusLabel>
<Button
btnAttrs={{
btnAttrs={
{
className: 'mailbox-link',
onClick: (e) => {
this.handleWatchInfo(e, `mailboxes/${mail.id}`, location);
onClick: (e) => Utils.handleLink(e, 'mailboxes/' + id)
}
}
}}
>
{mail.name}
{row.name}
</Button>
</td>
<td className={'mailbox-displayname'}>
{mail.name}
{row.name}
</td>
<td className={'mailbox-cos-plan'}>
<statusLabel className={'label-plan label-unknown'}>unknown</statusLabel>
<statusLabel className={'label-plan label-unknown'}>{'unknown'}</statusLabel>
</td>
<td>
......@@ -223,9 +304,7 @@ export default class Mailboxes extends React.Component {
btnAttrs={
{
className: 'btn btn-xs btn-default',
onClick: (e) => {
this.handleEdit(e, '/mailboxes/' + mail.id + '/edit');
}
onClick: (e) => Utils.handleLink(e, '/mailboxes/' + id + '/edit')
}
}
>
......@@ -234,11 +313,35 @@ export default class Mailboxes extends React.Component {
</td>
</tr>
);
});
}
makeTable(rows, page) {
const hasPage = page || false;
let pagination = null;
if (hasPage) {
pagination = (
<Pagination
key='panelPagination'
url='mailboxes'
currentPage={this.state.page}
totalPages={hasPage.total}
/>
);
}
if (Array.isArray(rows) && rows.length < 1) {
return (
<div className='text-center'>
<h4>
No existen resultados para su búsqueda
</h4>
</div>
);
}
const panelBody = (
return (
<div
key='mailboxes-body'
key='mailbox'
id='index-mailboxes-table'
className='table-responsive'
>
......@@ -257,51 +360,69 @@ export default class Mailboxes extends React.Component {
</tr>
</thead>
<tbody>
{tableResults}
{rows}
</tbody>
</table>
{pagination}
</div>
);
}
const todas = `Todas (${total})`;
const archiving = 'Archiving';
const noPlan = `Sin Plan (${total})`;
const locked = `Bloqueada (${arrLocked.length})`;
const arrTabNames = [
todas,
archiving,
noPlan,
locked
];
insertToPanel(table, id, btns, filter) {
const btn = btns || [];
const getFilter = filter || null;
let pagination;
if (this.state.offset > 0 || (this.state.data && this.state.data.more)) {
const totalPages = this.state.data ? Math.ceil(this.state.data.total / QueryOptions.DEFAULT_LIMIT) : 0;
const tab = this.props.location.query.tab || Utils.slug(todas);
pagination = (
<Pagination
key='panelPagination'
url={`mailboxes?tab=${tab}`}
currentPage={this.state.page}
totalPages={totalPages}
return (
<Panel
children={table}
key={id}
btnsHeader={btn}
filter={getFilter}
/>
);
}
buildTableFromData(data, arrayTabNames) {
if (data.account) {
const accounts = data.account;
const totalAccounts = data.total;
const limit = data.account.length;
let activeAccounts = [];
let lockedAccounts = [];
const tabs = {};
for (let i = 0; i < limit; i++) {
const account = accounts[i].attrs;
switch (account.zimbraAccountStatus) {
case 'active':
activeAccounts.push(this.buildRow(accounts[i], 'label label-success m-r', 'Activa'));
break;
case 'closed':
activeAccounts.push(this.buildRow(accounts[i], 'label label-default m-r', 'Cerrada'));
break;
case 'locked':
activeAccounts.push(this.buildRow(accounts[i], 'label label-warning m-r', 'Inactiva'));
break;
case 'lockedout':
lockedAccounts.push(this.buildRow(accounts[i], 'label label-locked m-r', 'Bloqueada'));
activeAccounts.push(this.buildRow(accounts[i], 'label label-locked m-r', 'Bloqueada'));
break;
}
}
const response = {};
const all = `${arrayTabNames.shift()} (${activeAccounts.length})`;
const locked = `${arrayTabNames.shift()} (${lockedAccounts.length})`;
// create structure html for all accountsç
const icon = (
<div>
<i className='/fa fa-download'/>
<i className='fa fa-download'/>
<span>{'Exportar'}</span>
</div>
);
const tab1 = (
<div>
<Panel
title=''
children={[panelBody, pagination]}
btnsHeader={[
const btn = [
{
props: {
className: 'btn btn-default',
......@@ -311,85 +432,154 @@ export default class Mailboxes extends React.Component {
},
label: icon
},
{
setComponent: (
<ToggleModalButton
role='button'
className='btn btn-info hide-xs hide-sm'
dialogType={ImportMassiveModal}
dialogProps={{
data: this.state.data
}}
key='change-passwd-import'
>
{'Importar'}
</ToggleModalButton>
)
},
{
props: {
className: 'btn btn-success',
onClick: (e) => {
this.handleAddMailbox(e, '/mailboxes/new');
}
onClick: (e) => Utils.handleLink(e, '/mailboxes/new')
},
label: '+ Nueva Casilla'
}
]}
/>
];
let activePagination = null;
const totalPage = Math.ceil(totalAccounts / QueryOptions.DEFAULT_LIMIT);
if (activeAccounts.length > QueryOptions.DEFAULT_LIMIT) {
activeAccounts = activeAccounts.slice(this.state.offset, (this.state.page * QueryOptions.DEFAULT_LIMIT));
activePagination = {
total: totalPage
};
}
let lockedPagination = null;
if (lockedAccounts.length > QueryOptions.DEFAULT_LIMIT) {
lockedAccounts = lockedAccounts.slice(this.state.offset, (this.state.page * QueryOptions.DEFAULT_LIMIT));
lockedPagination = {
total: totalPage
};
}
const filter = (
<div>
<div className='input-group pull-left'>
<select
className='form-control status'
onChange={this.handleChangeFilter}
>
<option value=''>Todas</option>
<option value='active'>Activa</option>
<option value='locked'>Inactiva</option>
<option value='lockedout'>Bloqueada</option>
<option value='closed'>Cerradas</option>
</select>
</div>
<div className='input-group'>
<select
className='form-control plans'
onChange={this.handleChangeFilter}
>
<option value=''>Todoas los planes</option>
<option value='basic'>Básico</option>
<option value='professional'>Profesional</option>
<option value='premium'>Premium</option>
</select>
</div>
</div>
);
const tab2 = (
<Panel
title='Casillas tab2'
children={[panelBody, pagination]}
const tableActive = this.makeTable(activeAccounts, activePagination);
const panelActive = this.insertToPanel(tableActive, 'panel-all', btn, filter);
// create structure html for all locked accounts
const tableLocked = this.makeTable(lockedAccounts, lockedPagination);
const panelLocked = this.insertToPanel(tableLocked, 'panel-locked');
arrayTabNames.push(all, locked);
tabs[Utils.slug(all)] = panelActive;
tabs[Utils.slug(locked)] = panelLocked;
response.tabNames = arrayTabNames;
response.tabs = tabs;
if (lockedAccounts.length > 0) {
const isPlural = (lockedAccounts.length > 1) ? true : null;
response.lockedAlert = {
total: lockedAccounts.length,
message: (isPlural) ? `${lockedAccounts.length} casillas bloqueadas` : `${lockedAccounts.length} casilla bloqueada`
};
}
return response;
}
return false;
}
render() {
let message = null;
let content = null;
if (this.state.error) {
message = (
<MessageBar
message={this.state.error}
type={this.state.type}
autoclose={false}
/>
);
}
const tab3 = (
<Panel
title='Casillas tb3'
children={panelBody}
/>
if (this.state.notMatches) {
const domain = (this.state.domain) ? `para el dominio: ${this.state.domain}` : '';
content = (
<div className='text-center'>
<h4>
{`No se han encontrado resultados ${domain}`}
</h4>
</div>
);
}
const tab4 = (
<Panel
title='Casillas tab4'
children={panelBody}
const pagelInfo = (
<PageInfo
titlePage='Casillas'
descriptionPage='Usuarios de correo electrónico'
/>
);
const arrTabs = {};
arrTabs[Utils.slug(todas)] = tab1;
arrTabs[Utils.slug(archiving)] = tab2;
arrTabs[Utils.slug(noPlan)] = tab3;
arrTabs[Utils.slug(locked)] = tab4;
if (this.state.data) {
const data = this.state.data;
panelTabs = (
content = (
<PanelTab
tabNames={arrTabNames}
tabs={arrTabs}
tabNames={data.tabNames}
tabs={data.tabs}
location={this.props.location}
onClick={this.handleTabChanged}
/>
);
}
const content = panelTabs || '';
let title = 'Casillas';
const domain = DomainStore.getCurrent();
if (this.props.params.domain_id && domain) {
title = (
<div>
{'Casillas del dominio: '}
<a
href='#'
onClick={(e) => Utils.handleLink(e, `/domains/${domain.id}`)}
>
{`@${domain.name}`}
</a>
</div>
);
}
return (
<div>
<PageInfo
titlePage={title}
descriptionPage='Usuarios de correo electrónico'
/>
{message}
{pagelInfo}
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-12 panel-with-tabs'>
{content}
......
......@@ -12,14 +12,19 @@ import Panel from '../panel.jsx';
import BlockGeneralInfoMailbox from './info_general_mailbox.jsx';
import BlockStatsMailbox from './stats_mailbox.jsx';
import FormVacacionesMailbox from './form_resp_vacaciones_mailbox.jsx';
import FormAliasMailbox from './form_alias_mailbox.jsx';
import FormAliasMailbox from './../panel_actions.jsx';
import ChangePasswordModal from './change_passwd_modal.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import MessageBar from '../message_bar.jsx';
import Promise from 'bluebird';
import MailboxStore from '../../stores/mailbox_store.jsx';
import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import Constants from '../../utils/constants.jsx';
const MessageTypes = Constants.MessageType;
export default class MailboxDetails extends React.Component {
constructor(props) {
......@@ -28,6 +33,8 @@ export default class MailboxDetails extends React.Component {
this.getMailbox = this.getMailbox.bind(this);
this.handleEdit = this.handleEdit.bind(this);
this.showMessage = this.showMessage.bind(this);
this.onRemoveAlias = this.onRemoveAlias.bind(this);
this.onCancelAlias = this.onCancelAlias.bind(this);
this.state = {};
}
......@@ -36,40 +43,119 @@ export default class MailboxDetails extends React.Component {
Utils.handleLink(e, path, location);
}
exportAsCSV(data) {
Utils.exportAsCSV(data, 'mailboxes_alias', true);
}
showMessage(attrs) {
this.setState({
error: attrs.message,
type: attrs.type.toLowerCase(),
autocloseInSecs: 10
error: attrs.error,
type: attrs.typeError
});
}
getMailbox() {
const id = this.props.params.id;
Utils.toggleStatusButtons('.action-info-btns', true);
onRemoveAlias(alias) {
MailboxStore.removeAlias(alias);
const items = MailboxStore.getAlias();
Client.getAccount(id, (data) => {
this.setState({
data
alias: items,
error: false
});
}
onCancelAlias(arrAlias) {
MailboxStore.refreshAlias(arrAlias);
const items = MailboxStore.getAlias();
this.setState({
alias: items,
error: false
});
}
getMailbox(id) {
if (MailboxStore.hasMailboxes()) {
const account = MailboxStore.getMailboxById(id);
MailboxStore.setCurrent(account);
let items = account.attrs.zimbraMailAlias;
if (items) {
if (!Array.isArray(items)) {
items = [items];
}
}
account.viewMailPath(global.window.manager_config.webmailLifetime, (error, res) => {
if (res) {
return this.setState({
data: account,
alias: items,
webmail: `${global.window.manager_config.webMailUrl}${res}`
});
}
this.setState({
data: account,
alias: items,
webmail: false
});
});
GlobalActions.emitEndLoading();
Utils.toggleStatusButtons('.action-info-btns', false);
} else {
return new Promise((resolve, reject) => {
Client.getAccount(id, (data) => {
return resolve(data);
}, (error) => {
this.setState({
notFound: true,
message: error.message
return reject(error);
});
}).then((result) => {
MailboxStore.setCurrent(result);
let items = result.attrs.zimbraMailAlias;
if (items) {
if (!Array.isArray(items)) {
items = [items];
}
}
result.viewMailPath(global.window.manager_config.webmailLifetime, (error, res) => {
if (res) {
return this.setState({
data: result,
alias: items,
webmail: `${global.window.manager_config.webMailUrl}${res}`
});
}
this.setState({
data: result,
alias: items,
webmail: false
});
});
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
type: error.typeError
});
}).finally(() => {
GlobalActions.emitEndLoading();
Utils.toggleStatusButtons('.action-info-btns', false);
});
}
return true;
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
$('#sidebar-mailboxes').addClass('active');
this.getMailbox();
this.getMailbox(this.props.params.id);
}
componentWillUnmount() {
......@@ -77,6 +163,107 @@ export default class MailboxDetails extends React.Component {
$('#sidebar-mailboxes').removeClass('active');
}
onAliasSubmit(response) {
if (response.refresh) {
MailboxStore.refreshAlias(response.refresh);
}
this.multipleActions(response, this.state.data).then((result) => {
const limit = result.length;
const errors = [];
for (let index = 0; index < limit; index++) {
const res = result[index];
if (result[index].error) {
const action = (res.action === 'remove') ? 'eliminar' : 'agregar';
errors.push({
error: `Hubo un error al ${action} el alias: ${res.item}, debido a ${res.error.extra.reason}`,
type: MessageTypes.ERROR
});
}
if (res.isCompleted && res.action === 'add') {
MailboxStore.setAlias(res.item);
}
}
if (errors.length !== limit) {
errors.push({
error: 'Se han guardado los datos éxitosamente.',
type: MessageTypes.SUCCESS
});
}
this.setState({
error: errors,
alias: MailboxStore.getAlias()
});
response.reset();
});
}
multipleActions(response, account) {
const promises = [];
for (const key in response) {
if (response.hasOwnProperty(key) && key === 'add') {
const array = response[key];
const limit = array.length;
for (let index = 0; index < limit; index++) {
const res = {};
const promesa = new Promise((resolve) => {
account.addAccountAlias(array[index], (error) => {
if (error) {
res.isCompleted = false;
res.item = response[key][index];
res.action = key;
res.error = error;
} else {
res.isCompleted = true;
res.item = response[key][index];
res.action = key;
}
return resolve(res);
});
});
promises.push(promesa);
}
}
if (response.hasOwnProperty(key) && key === 'remove') {
const array = response[key];
const limit = array.length;
for (let index = 0; index < limit; index++) {
const res = {};
const promesa = new Promise((resolve) => {
account.removeAccountAlias(array[index], (error) => {
if (error) {
res.isCompleted = false;
res.item = response[key][index];
res.action = key;
res.error = error;
} else {
res.isCompleted = true;
res.item = response[key][index];
res.action = key;
}
return resolve(res);
});
});
promises.push(promesa);
}
}
}
return Promise.all(promises);
}
render() {
let generalData;
let statsData;
......@@ -86,6 +273,18 @@ export default class MailboxDetails extends React.Component {
let message;
if (this.state.error) {
if (Array.isArray(this.state.error)) {
message = this.state.error.map((err, i) => {
return (
<MessageBar
key={`new-error-${i}`}
message={err.error}
type={err.type}
autoclose={true}
/>
);
});
} else {
message = (
<MessageBar
message={this.state.error}
......@@ -94,6 +293,7 @@ export default class MailboxDetails extends React.Component {
/>
);
}
}
if (this.state.data) {
generalData = (
......@@ -136,14 +336,29 @@ export default class MailboxDetails extends React.Component {
}
];
if (this.state.webmail) {
btnsStats = [
{
props: {
className: 'btn btn-xs btn-default action-info-btns',
target: '_blank',
href: this.state.webmail
},
label: 'Ver Correos'
}
];
} else {
btnsStats = [
{
props: {
className: 'btn btn-xs btn-default action-info-btns'
className: 'btn btn-xs btn-default disabled',
title: 'Hubo un error al obtener el acceso al mail',
disabled: 'disabled'
},
label: 'Ver Correos'
}
];
}
const formAutoResp = (
<FormVacacionesMailbox data={this.state.data}/>
......@@ -151,7 +366,18 @@ export default class MailboxDetails extends React.Component {
const formAlias = (
<FormAliasMailbox
data={this.state.data}
name={'alias'}
data={this.state.alias}
isEmail={true}
onDelete={(alias) => {
this.onRemoveAlias(alias);
}}
onCancel={(arrAlias) => {
this.onCancelAlias(arrAlias);
}}
onApplyChanges={(response) => {
this.onAliasSubmit(response);
}}
/>
);
......@@ -204,8 +430,8 @@ export default class MailboxDetails extends React.Component {
return (
<div>
{pageInfo}
{message}
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-6 central-content'>
<Panel
......
......@@ -7,33 +7,62 @@ export default class BlockGeneralInfoMailbox extends React.Component {
this.date = null;
this.status = null;
this.className = null;
this.lastConection = 'no se ha conectado';
this.lastConection = 'No se ha conectado';
this.getMailSize = this.getMailSize.bind(this);
this.state = {};
}
getMailSize() {
this.props.data.getMailboxSize((err, bytes) => {
let currentSize = '0 MB';
if (bytes) {
currentSize = Utils.bytesToMegas(bytes);
}
this.setState({
size: currentSize
});
});
}
componentWillMount() {
this.date = Utils.dateFormatted(this.props.data.attrs.zimbraCreateTimestamp);
switch (this.props.data.attrs.zimbraAccountStatus) {
case 'inactive':
this.status = 'Desactivada';
this.className = 'btn btn-md btn-default';
break;
case 'locked':
case 'lockedout':
this.status = 'Bloqueada';
this.className = 'btn btn-md btn-primary2';
this.className = 'label-locked mailbox-status';
break;
default:
case 'active':
this.status = 'Activa';
this.className = 'btn btn-md btn-info';
this.className = 'label-success mailbox-status';
break;
case 'closed':
this.status = 'Cerrada';
this.className = 'label-default mailbox-status';
break;
case 'locked':
this.status = 'Inactiva';
this.className = 'label-warning mailbox-status';
break;
default:
}
if (this.props.data.attrs.zimbraLastLogonTimestamp) {
this.lastConection = Utils.dateFormatted(this.props.data.attrs.zimbraLastLogonTimestamp);
}
this.getMailSize();
}
render() {
let size = null;
if (this.state.size) {
size = this.state.size;
}
return (
<div>
<div className='row'>
......@@ -59,14 +88,14 @@ export default class BlockGeneralInfoMailbox extends React.Component {
<div>
<p>
<span className='center-block'>Espacio Usado</span>
<strong>0 Bytes</strong>
<strong>{size}</strong>
</p>
</div>
</div>
<div className='col-xs-6'>
<div>
<p>
<span className='center-block'>Últimas Conexión</span>
<span className='center-block'>Última Conexión</span>
<strong>{this.lastConection}</strong>
</p>
</div>
......
......@@ -112,7 +112,7 @@ MessageBar.defaultProps = {
MessageBar.propTypes = {
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']),
canClose: React.PropTypes.bool,
autoclose: React.PropTypes.bool,
......
......@@ -123,7 +123,7 @@ export default class Pagination extends React.Component {
return (
<div id='pagination'>
<ul className='pagination'>
<ul className='pagination pagination-sm'>
{first}
{prev}
{pages}
......
......@@ -22,8 +22,8 @@ export default class Panel extends React.Component {
panelHeader = (
<div className='panel-heading hbuilt clearfix'>
<div className='pull-right'>{btns}</div>
<div className='heading-buttons'>
{this.props.title}
<div className='heading-buttons pull-left'>
{this.props.title || this.props.filter}
</div>
</div>
);
......@@ -47,7 +47,8 @@ Panel.propTypes = {
title: React.PropTypes.string,
classHeader: React.PropTypes.string,
error: React.PropTypes.element,
children: React.PropTypes.any
children: React.PropTypes.any,
filter: React.PropTypes.element
};
Panel.defaultProps = {
......
//import Datalist from 'react-datalist';
import React from 'react';
import Button from './button.jsx';
import PaginateArray from '../stores/paginate_array_store.jsx';
import DataList from 'react-datalist';
import * as Utils from '../utils/utils.jsx';
import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
const messageType = Constants.MessageType;
export default class PanelActions extends React.Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
this.handleAddNew = this.handleAddNew.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleChangeLimit = this.handleChangeLimit.bind(this);
this.handleOnExport = this.handleOnExport.bind(this);
this.onSearch = this.onSearch.bind(this);
this.reset = this.reset.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
const limit = 10;
this.pagination = new PaginateArray(this.props.data, limit, 1);
this.add = [];
this.remove = [];
this.forAdd = [];
this.forRemove = [];
this.refresh = [];
const states = {};
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.state = states;
}
handleOnExport(e) {
e.preventDefault();
if (this.props.onExport) {
return this.props.onExport(this.state['items' + this.props.name]);
}
throw new Error('onExport function was not defined, onExport :' + this.props.onExport);
}
onSearch() {
const search = this.refs.search.value;
const arrayFiltered = this.props.data.filter((strArray) => {
if (strArray.match(search)) {
return strArray;
}
return false;
});
const states = {};
this.pagination.setArray(arrayFiltered);
this.pagination.reset();
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
prev() {
const prev = this.pagination.prevPage();
if (prev) {
const states = {};
states['items' + this.props.name] = prev;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
}
next() {
const next = this.pagination.nextPage();
if (next) {
const states = {};
states['items' + this.props.name] = next;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
}
handleDelete(e, item) {
e.preventDefault();
this.remove.push(item);
this.forRemove.push(item);
this.props.onDelete(item);
}
reset() {
this.add = [];
this.remove = [];
this.forAdd = [];
this.forRemove = [];
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
}
handleAddNew(e) {
e.preventDefault();
let item = null;
Utils.validateInputRequired(this.refs).then(() => {
if (this.props.hasComboInput) {
const domain = document.querySelector('input[list=\'domain\']');
if (domain.value === '') {
GlobalActions.emitMessage({
error: 'El dominio es requerido, verifique por favor.',
typeError: messageType.ERROR
});
domain.focus();
return false;
}
item = this.refs.addInput.value + '@' + domain.value;
if (this.props.isEmail) {
const emailPattern = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
const test = emailPattern.test(item);
if (!test) {
GlobalActions.emitMessage({
error: 'El correo no es correcto, verifiquelo por favor.',
typeError: messageType.ERROR
});
return false;
}
}
this.add.push(item);
this.forAdd.push(item);
} else {
item = this.refs.addInput.value;
if (this.props.isEmail) {
const emailPattern = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
const test = emailPattern.test(item);
if (!test) {
GlobalActions.emitMessage({
error: 'El correo no es correcto, verifíquelo por favor.',
typeError: messageType.ERROR
});
this.refs.addInput.focus();
return false;
}
}
this.refs.addInput.value = '';
this.add.push(item);
this.forAdd.push(item);
}
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
return true;
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
typeError: messageType.ERROR
});
error.node.focus();
});
}
componentWillReceiveProps(nextProps) {
this.pagination.setArray(nextProps.data);
this.pagination.reset();
const states = {};
states['items' + this.props.name] = nextProps.data;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
handleChangeLimit() {
const limit = this.refs.countItems.value;
this.pagination.setLimit(limit);
this.pagination.reset();
const states = {};
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
handleChange(e, item, action) {
if (action === 'remove') {
if (e.target.checked) {
this.forRemove.push(item);
this.getOutItemFromArray(this.refresh, item);
} else {
this.getOutItemFromArray(this.forRemove, item);
this.refresh.push(item);
}
return null;
}
if (e.target.checked) {
this.forAdd.push(item);
} else {
this.getOutItemFromArray(this.forAdd, item);
}
return null;
}
getOutItemFromArray(array, item) {
const length = array.length;
if (length > 0) {
for (let i = 0; i < length; i++) {
if (item === array[i]) {
array.splice(i, 1);
return true;
}
}
}
return false;
}
handleCancel() {
this.props.onCancel(this.remove);
this.remove = [];
this.add = [];
this.forRemove = [];
this.forAdd = [];
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
}
onSubmit() {
const response = {};
response.reset = this.reset;
this.refs.savebutton.setAttribute('disabled', 'disabled');
this.refs.savebutton.innerHTML = 'Aplicando Cambios...';
if (this.forAdd.length > 0) {
response.add = this.forAdd;
}
if (this.forRemove.length > 0) {
response.remove = this.forRemove;
}
if (this.refresh.length > 0) {
response.refresh = this.refresh;
}
this.props.onApplyChanges(response);
}
render() {
let rows = null;
let showName = `Agregar ${this.props.name}`;
let itemsForRemove = [];
let itemsForAdd = [];
let actionButtons = null;
let pagination = null;
let buttonExport = null;
let input = (
<div className='input-group'>
<input
type='text'
className='form-control'
placeholder={`${this.props.name}`}
ref='addInput'
data-required='true'
data-message={`${this.props.name} no pueda estar vacio, verifique por favor`}
/>
<span className='input-group-btn'>
<Button
btnAttrs={
{
className: 'btn btn-default pending_actions',
onClick: (e) => {
this.handleAddNew(e);
}
}
}
>
{showName}
</Button>
</span>
</div>
);
if (this.props.hasComboInput) {
input = (
<div className='input-group'>
<input
type='text'
ref='addInput'
className='form-control'
placeholder={this.props.name}
data-required='true'
data-message={`${this.props.name} no pueda estar vacio, verifique por favor`}
/>
<span className='input-group-addon'>
{'@'}
</span>
<DataList
list='dominio'
options={this.props.options}
placeHolder='Dominio'
/>
<span className='input-group-btn'>
<Button
btnAttrs={
{
className: 'btn btn-default pending_actions',
onClick: this.handleAddNew
}
}
>
{showName}
</Button>
</span>
</div>
);
}
if (this.state['items' + this.props.name] === 'undefined' || this.state['items' + this.props.name].length < 1) {
rows = (
<tr>
<td className='text-center'>
<strong>
{`No hay resultados para ${Utils.titleCase(this.props.name)}`}
</strong>
</td>
</tr>
);
} else {
const data = this.state['items' + this.props.name];
rows = data.map((row, index) => {
return (
<tr key={index}>
<td>
{row}
<Button
btnAttrs={
{
className: 'pull-right',
onClick: (e) => {
this.handleDelete(e, row);
}
}
}
>
<i className='fa fa-minus-circle text-danger'></i>
</Button>
</td>
</tr>
);
});
}
// make list adding / removing items
if (this.add.length > 0 || this.remove.length > 0) {
actionButtons = (
<div className='actions-buttons text-right'>
<button
className='btn btn-default pending_actions'
onClick={this.handleCancel}
>
{'Cancelar'}
</button>
<button
className='btn btn-primary pending_actions applyButtons'
onClick={() => {
this.onSubmit();
}}
ref='savebutton'
>
{'Guardar Cambios'}
</button>
</div>
);
if (this.add.length > 0) {
itemsForAdd = this.add.map((element, key) => {
return (
<label
className='list-inline listed-field'
key={`${this.props.name}labeladd${key}`}
>
<input
type='checkbox'
defaultChecked={'checked'}
data-value={element}
onChange={(e) => {
this.handleChange(e, element, 'add');
}}
/>
{element}
</label>
);
});
itemsForAdd = (
<div className='new-fields'>
<h5>Por Agregar</h5>
<div
className='new-fields-list'
>
{itemsForAdd}
</div>
</div>
);
}
if (this.remove.length > 0) {
itemsForRemove = this.remove.map((element, key) => {
return (
<label
className='list-inline listed-field'
key={`${this.props.name}labelremove${key}`}
>
<input
type='checkbox'
defaultChecked={'checked'}
data-value={element}
onChange={(e) => {
this.handleChange(e, element, 'remove');
}}
/>
{element}
</label>
);
});
itemsForRemove = (
<div className='new-fields'>
<h5>Por Eliminar</h5>
<div
className='new-fields-list'
>
{itemsForRemove}
</div>
</div>
);
}
}
if (this.state['pagination' + this.props.name]) {
pagination = this.state['pagination' + this.props.name];
}
if (this.props.hasExport && this.state['items' + this.props.name].length > 0) {
const icon = (
<div>
<i className='fa fa-download'/>
<span>{'Exportar'}</span>
</div>
);
buttonExport = (
<Button
btnAttrs={
{
className: 'btn btn-default',
onClick: (e) => {
this.handleOnExport(e);
}
}
}
>
{icon}
</Button>
);
}
return (
<div>
<div className='row'>
<div className='col-xs-6 clearfix'>
<div className='row'>
<form className='form-inline'>
<div className='col-xs-4'>
<div className='form-group'>
<label htmlFor='select-pages'>
{'Mostrar'}
<select
id='select-pages'
className='form-control input-sm margin-left'
ref='countItems'
onChange={this.handleChangeLimit}
>
<option value='10'>{'10'}</option>
<option value='25'>{'25'}</option>
<option value='50'>{'50'}</option>
<option value='100'>{'100'}</option>
</select>
</label>
</div>
</div>
<div className='col-xs-8 text-right'>
<label htmlFor='search'>
{'Buscar'}
<input
type='text'
className='form-control input-sm margin-left'
ref='search'
onKeyUp={() => {
this.onSearch();
}}
/>
</label>
</div>
</form>
</div>
<div className='col-xs-12'>
<div className='row'>
<table className='table table-striped table-bordered table-hover dataTable no-footer'>
<thead>
<tr>
<th>
{'Nombre'}
<span className='pull-right'>
<i
className='glyphicon pull-right pointer'
>
</i>
</span>
</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
</div>
<div className='col-xs-12 clearfix'>
<div className='row'>
<div className='dataTables_info pull-left'>
{buttonExport}
</div>
<div className='btn-group pull-right as-table'>
<span className='as-cell'>
{pagination}
</span>
<Button
btnAttrs={
{
className: 'btn btn-default pag-prev',
onClick: () => {
this.prev();
}
}
}
>
{'Anterior'}
</Button>
<Button
btnAttrs={
{
className: 'btn btn-default pag-next',
onClick: () => {
this.next();
}
}
}
>
{'Siguiente'}
</Button>
</div>
</div>
</div>
</div>
<div className='col-xs-6'>
<div className='col-xs-12'>
<div className='row'>
{input}
<div
className='wrap-controls'
ref='parent'
>
{itemsForAdd}
{itemsForRemove}
{actionButtons}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
PanelActions.propTypes = {
name: React.PropTypes.string.isRequired,
onApplyChanges: React.PropTypes.func.isRequired,
data: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.string
]),
options: React.PropTypes.array,
hasComboInput: React.PropTypes.bool,
onAdd: React.PropTypes.func,
onDelete: React.PropTypes.func,
onCancel: React.PropTypes.func,
hasExport: React.PropTypes.bool,
showNameOnButton: React.PropTypes.bool,
onExport: React.PropTypes.func,
isEmail: React.PropTypes.bool
};
PanelActions.defaultProps = {
hasComboInput: false,
hasExport: false,
showNameOnButton: true,
options: [],
isEmail: false
};
//import Datalist from 'react-datalist';
import React from 'react';
import Button from './button.jsx';
import PaginateArray from '../stores/paginate_array_store.jsx';
import DataList from 'react-datalist';
import * as Utils from '../utils/utils.jsx';
import * as GlobalActions from '../action_creators/global_actions.jsx';
import Constants from '../utils/constants.jsx';
const messageType = Constants.MessageType;
export default class PanelActions extends React.Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
this.handleAddNew = this.handleAddNew.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleChangeLimit = this.handleChangeLimit.bind(this);
this.handleOnExport = this.handleOnExport.bind(this);
this.onSearch = this.onSearch.bind(this);
this.reset = this.reset.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
const limit = 10;
this.pagination = new PaginateArray(this.props.data, limit, 1);
this.add = [];
this.remove = [];
this.forAdd = [];
this.forRemove = [];
this.refresh = [];
const states = {};
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.state = states;
}
handleOnExport(e) {
e.preventDefault();
if (this.props.onExport) {
return this.props.onExport(this.state['items' + this.props.name]);
}
throw new Error('onExport function was not defined, onExport :' + this.props.onExport);
}
onSearch() {
const search = this.refs.search.value;
const arrayFiltered = this.props.data.filter((strArray) => {
if (strArray.match(search)) {
return strArray;
}
return false;
});
const states = {};
this.pagination.setArray(arrayFiltered);
this.pagination.reset();
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
prev() {
const prev = this.pagination.prevPage();
if (prev) {
const states = {};
states['items' + this.props.name] = prev;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
}
next() {
const next = this.pagination.nextPage();
if (next) {
const states = {};
states['items' + this.props.name] = next;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
}
handleDelete(e, item) {
e.preventDefault();
this.remove.push(item);
this.forRemove.push(item);
this.props.onDelete(item);
}
reset() {
this.add = [];
this.remove = [];
this.forAdd = [];
this.forRemove = [];
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
}
handleAddNew(e) {
e.preventDefault();
let item = null;
Utils.validateInputRequired(this.refs).then(() => {
if (this.props.hasComboInput) {
const domain = document.querySelector('input[list=\'domain\']');
if (domain.value === '') {
GlobalActions.emitMessage({
error: 'El dominio es requerido, verifique por favor.',
typeError: messageType.ERROR
});
domain.focus();
return false;
}
item = this.refs.addInput.value + '@' + domain.value;
if (this.props.isEmail) {
const emailPattern = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
const test = emailPattern.test(item);
if (!test) {
GlobalActions.emitMessage({
error: 'El correo no es correcto, verifiquelo por favor.',
typeError: messageType.ERROR
});
return false;
}
}
this.add.push(item);
this.forAdd.push(item);
} else {
item = this.refs.addInput.value;
if (this.props.isEmail) {
const emailPattern = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
const test = emailPattern.test(item);
if (!test) {
GlobalActions.emitMessage({
error: 'El correo no es correcto, verifíquelo por favor.',
typeError: messageType.ERROR
});
this.refs.addInput.focus();
return false;
}
}
this.refs.addInput.value = '';
this.add.push(item);
this.forAdd.push(item);
}
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
return true;
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
typeError: messageType.ERROR
});
error.node.focus();
});
}
componentWillReceiveProps(nextProps) {
this.pagination.setArray(nextProps.data);
this.pagination.reset();
const states = {};
states['items' + this.props.name] = nextProps.data;
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
handleChangeLimit() {
const limit = this.refs.countItems.value;
this.pagination.setLimit(limit);
this.pagination.reset();
const states = {};
states['items' + this.props.name] = this.pagination.init();
states['pagination' + this.props.name] = this.pagination.getResults();
this.setState(states);
}
handleChange(e, item, action) {
if (action === 'remove') {
if (e.target.checked) {
this.forRemove.push(item);
this.getOutItemFromArray(this.refresh, item);
} else {
this.getOutItemFromArray(this.forRemove, item);
this.refresh.push(item);
}
} else if (e.target.checked) {
this.forAdd.push(item);
} else {
this.getOutItemFromArray(this.forAdd, item);
}
}
getOutItemFromArray(array, item) {
const length = array.length;
if (length > 0) {
for (let i = 0; i < length; i++) {
if (item === array[i]) {
array.splice(i, 1);
return true;
}
}
}
return false;
}
handleCancel() {
this.props.onCancel(this.remove);
this.remove = [];
this.add = [];
this.forRemove = [];
this.forAdd = [];
const states = {};
states['change' + this.props.name] = true;
this.setState(states);
}
onSubmit() {
const response = {};
response.reset = this.reset;
this.refs.savebutton.setAttribute('disabled', 'disabled');
this.refs.savebutton.innerHTML = 'Aplicando Cambios...';
if (this.forAdd.length > 0) {
response.add = this.forAdd;
}
if (this.forRemove.length > 0) {
response.remove = this.forRemove;
}
if (this.refresh.length > 0) {
response.refresh = this.refresh;
}
this.props.onApplyChanges(response);
}
render() {
let rows = null;
let showName = `Agregar ${this.props.name}`;
let itemsForRemove = [];
let itemsForAdd = [];
let actionButtons = null;
let pagination = null;
let buttonExport = null;
let input = (
<div className='input-group'>
<input
type='text'
className='form-control'
placeholder={`${this.props.name}`}
ref='addInput'
data-required='true'
data-message={`${this.props.name} no pueda estar vacio, verifique por favor`}
/>
<span className='input-group-btn'>
<Button
btnAttrs={
{
className: 'btn btn-default pending_actions',
onClick: (e) => {
this.handleAddNew(e);
}
}
}
>
{showName}
</Button>
</span>
</div>
);
if (this.props.hasComboInput) {
input = (
<div className='input-group'>
<input
type='text'
ref='addInput'
className='form-control'
placeholder={this.props.name}
data-required='true'
data-message={`${this.props.name} no pueda estar vacio, verifique por favor`}
/>
<span className='input-group-addon'>
{'@'}
</span>
<DataList
list='dominio'
options={this.props.options}
placeHolder='Dominio'
/>
<span className='input-group-btn'>
<Button
btnAttrs={
{
className: 'btn btn-default pending_actions',
onClick: this.handleAddNew
}
}
>
{showName}
</Button>
</span>
</div>
);
}
if (this.state['items' + this.props.name] === 'undefined' || this.state['items' + this.props.name].length < 1) {
rows = (
<tr>
<td className='text-center'>
<strong>
{`No hay resultados para ${Utils.titleCase(this.props.name)}`}
</strong>
</td>
</tr>
);
} else {
const data = this.state['items' + this.props.name];
rows = data.map((row, index) => {
return (
<tr key={index}>
<td>
{row}
<Button
btnAttrs={
{
className: 'pull-right',
onClick: (e) => {
this.handleDelete(e, row);
}
}
}
>
<i className='fa fa-minus-circle text-danger'></i>
</Button>
</td>
</tr>
);
});
}
// make list adding / removing items
if (this.add.length > 0 || this.remove.length > 0) {
actionButtons = (
<div className='actions-buttons text-right'>
<button
className='btn btn-default pending_actions'
onClick={this.handleCancel}
>
{'Cancelar'}
</button>
<button
className='btn btn-primary pending_actions applyButtons'
onClick={() => {
this.onSubmit();
}}
ref='savebutton'
>
{'Guardar Cambios'}
</button>
</div>
);
if (this.add.length > 0) {
itemsForAdd = this.add.map((element, key) => {
return (
<label
className='list-inline listed-field'
key={`${this.props.name}labeladd${key}`}
>
<input
type='checkbox'
defaultChecked={'checked'}
data-value={element}
onChange={(e) => {
this.handleChange(e, element, 'add');
}}
/>
{element}
</label>
);
});
itemsForAdd = (
<div className='new-fields'>
<h5>Por Agregar</h5>
<div
className='new-fields-list'
>
{itemsForAdd}
</div>
</div>
);
}
if (this.remove.length > 0) {
itemsForRemove = this.remove.map((element, key) => {
return (
<label
className='list-inline listed-field'
key={`${this.props.name}labelremove${key}`}
>
<input
type='checkbox'
defaultChecked={'checked'}
data-value={element}
onChange={(e) => {
this.handleChange(e, element, 'remove');
}}
/>
{element}
</label>
);
});
itemsForRemove = (
<div className='new-fields'>
<h5>Por Eliminar</h5>
<div
className='new-fields-list'
>
{itemsForRemove}
</div>
</div>
);
}
}
if (this.state['pagination' + this.props.name]) {
pagination = this.state['pagination' + this.props.name];
}
if (this.props.hasExport && this.state['items' + this.props.name].length > 0) {
const icon = (
<div>
<i className='fa fa-download'/>
<span>{'Exportar'}</span>
</div>
);
buttonExport = (
<Button
btnAttrs={
{
className: 'btn btn-default',
onClick: (e) => {
this.handleOnExport(e);
}
}
}
>
{icon}
</Button>
);
}
return (
<div>
<div className='row'>
<div className='col-xs-6 clearfix'>
<div className='row'>
<form className='form-inline'>
<div className='col-xs-4'>
<div className='form-group'>
<label htmlFor='select-pages'>
{'Mostrar'}
<select
id='select-pages'
className='form-control input-sm margin-left'
ref='countItems'
onChange={this.handleChangeLimit}
>
<option value='10'>{'10'}</option>
<option value='25'>{'25'}</option>
<option value='50'>{'50'}</option>
<option value='100'>{'100'}</option>
</select>
</label>
</div>
</div>
<div className='col-xs-8 text-right'>
<label htmlFor='search'>
{'Buscar'}
<input
type='text'
className='form-control input-sm margin-left'
ref='search'
onKeyUp={() => {
this.onSearch();
}}
/>
</label>
</div>
</form>
</div>
<div className='col-xs-12'>
<div className='row'>
<table className='table table-striped table-bordered table-hover dataTable no-footer'>
<thead>
<tr>
<th>
{'Nombre'}
<span className='pull-right'>
<i
className='glyphicon pull-right pointer'
>
</i>
</span>
</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
</div>
<div className='col-xs-12 clearfix'>
<div className='row'>
<div className='dataTables_info pull-left'>
{buttonExport}
</div>
<div className='btn-group pull-right as-table'>
<span className='as-cell'>
{pagination}
</span>
<Button
btnAttrs={
{
className: 'btn btn-default pag-prev',
onClick: () => {
this.prev();
}
}
}
>
{'Anterior'}
</Button>
<Button
btnAttrs={
{
className: 'btn btn-default pag-next',
onClick: () => {
this.next();
}
}
}
>
{'Siguiente'}
</Button>
</div>
</div>
</div>
</div>
<div className='col-xs-6'>
<div className='col-xs-12'>
<div className='row'>
{input}
<div
className='wrap-controls'
ref='parent'
>
{itemsForAdd}
{itemsForRemove}
{actionButtons}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
PanelActions.propTypes = {
name: React.PropTypes.string.isRequired,
onApplyChanges: React.PropTypes.func.isRequired,
data: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.string
]),
options: React.PropTypes.array,
hasComboInput: React.PropTypes.bool,
onAdd: React.PropTypes.func,
onDelete: React.PropTypes.func,
onCancel: React.PropTypes.func,
hasExport: React.PropTypes.bool,
showNameOnButton: React.PropTypes.bool,
onExport: React.PropTypes.func,
isEmail: React.PropTypes.bool
};
PanelActions.defaultProps = {
hasComboInput: false,
hasExport: false,
showNameOnButton: true,
options: [],
isEmail: false
};
......@@ -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) {
e.preventDefault();
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import EventStore from '../stores/event_store.jsx';
import * as GlobalActions from '../action_creators/global_actions.jsx';
export default class ProgressTask extends React.Component {
constructor(props) {
super(props);
this.showTasks = this.showTasks.bind(this);
this.removeTask = this.removeTask.bind(this);
this.tasks = [];
this.state = {};
}
componentWillUnmount() {
EventStore.removeTaskSListener(this.showTasks);
EventStore.removeEndTaskSListener(this.removeTask);
}
componentDidMount() {
EventStore.addTaskSListener(this.showTasks);
EventStore.addEndTaskSListener(this.removeTask);
}
removeTask(params) {
if (this.tasks.length > 0) {
const arrTasks = this.tasks;
const length = arrTasks.length;
for (let i = 0; i < length; i++) {
if (arrTasks[i].id === params.id) {
this.tasks.splice(i, 1);
EventStore.emitToast({
type: 'success',
title: params.toast.title,
body: params.toast.message,
options: {
timeOut: 10000,
extendedTimeOut: 5000,
closeButton: true
}
});
break;
}
}
}
if (this.tasks.length < 1) {
return this.setState({
show: null
});
}
return this.setState({
tasks: this.tasks,
total: this.tasks.length
});
}
showTasks(params) {
this.tasks.push(params);
this.setState({
show: true,
tasks: this.tasks,
total: this.tasks.length
});
}
render() {
let tasks = null;
let show = null;
let message = 'Acciones en ejecución';
if (this.state.show) {
show = 'active';
}
if (this.state.tasks) {
const taskList = this.state.tasks;
tasks = [];
taskList.forEach((task, index) => {
tasks.push((
<ul
key={`ul-${index}`}
>
<li>{`${task.origin} - ${task.action}`}</li>
</ul>
));
});
}
if (this.state.total) {
const multiple = (this.state.total > 1) ? 'tareas' : 'tarea';
message = `${this.state.total} ${multiple} en ejecución`;
}
return (
<div className={`progress manager-progress ${show}`}>
<div className='taskboard alert-success'>
<div className='wrap-task'>
{tasks}
</div>
</div>
<div className={`progress-bar manager-task-bar progress-bar-striped ${show}`}>
{message}
</div>
</div>
);
}
}
ProgressTask.defaultProps = {
};
ProgressTask.propTypes = {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
};
import React from 'react';
export default class SelectCol extends React.Component {
constructor(props) {
super(props);
this.options = this.props.options;
this.disabledOptions = this.props.disabledOptions;
this.current = this.props.selected;
this.state = {
options: this.options
};
}
handleSelectChange(e, id) {
e.preventDefault();
if (this.props.onSelected) {
const option = e.target;
let selected = option.options[option.options.selectedIndex].text;
const restart = option.options[option.options.selectedIndex].hasAttribute('data-main');
if (selected) {
if (restart) {
selected = {
restart: this.current
};
this.current = null;
} else {
this.current = selected;
}
return this.props.onSelected(e, selected, id);
}
return null;
}
return null;
}
componentWillUnmount() {
this.setState({
options: null
});
}
render() {
let options = null;
const current = this.current;
if (this.state.options) {
const optionElement = this.state.options;
options = [];
options.push((
<option
key='header-option'
data-main={'true'}
>
{'Columna'}
</option>
));
for (const option in optionElement) {
if (optionElement.hasOwnProperty(option)) {
const isDisabled = (this.disabledOptions[option]) ? 'disabled' : null;
options.push((
<option
value={optionElement[option]}
key={`${option}-option`}
disabled={isDisabled}
>
{option}
</option>
));
}
}
}
return (
<select
{...this.props.selectAttrs}
onChange={(e) => {
this.handleSelectChange(e, this.props.id);
}}
defaultValue={current}
>
{options}
</select>
);
}
}
SelectCol.propTypes = {
options: React.PropTypes.object.isRequired,
selectAttrs: React.PropTypes.object,
onSelected: React.PropTypes.func,
disabledOptions: React.PropTypes.object,
selected: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.bool
]),
id: React.PropTypes.any.isRequired
};
SelectCol.defaultProps = {
options: []
};
{
"debug": true,
"debug": false,
"zimbraUrl": "http://zimbra.zboxapp.dev:8000/service/admin/soap",
"zimbraProxy": "https://zimbra.zboxapp.dev:7071",
"dnsApiUrl": "http://zimbra.zboxapp.dev:3000",
"webMailUrl": "https://zimbra.zboxapp.dev",
"plans": {
"basic": true,
"premium": true,
"professional": true,
"default": false
},
"webmailLifetime": 3600,
"companiesEndPoints": {
"list": "http://zimbra.zboxapp.dev:8001/list",
"detail": "http://zimbra.zboxapp.dev:8001/company/{id}",
......
......@@ -20,6 +20,8 @@ import Mailboxes from './components/mailbox/mailbox.jsx';
import MailboxDetails from './components/mailbox/mailbox_details.jsx';
import CreateMailBox from './components/mailbox/create_mailbox.jsx';
import EditMailBox from './components/mailbox/edit_mailbox.jsx';
import DistributionLists from './components/distribution/distribution_lists.jsx';
import EditDistributionList from './components/distribution/edit_distribution_lists.jsx';
import * as Client from './utils/client.jsx';
import * as Utils from './utils/utils.jsx';
......@@ -77,12 +79,7 @@ function onPreLoggedIn(nextState, replace, callback) {
global.window.Zimbra = ZimbraStore.getCurrent();
}
const cos = ZimbraStore.getAllCos();
if (cos) {
return callback();
}
return Client.getAllCos(
Client.getAllCos(
(cosData) => {
ZimbraStore.setAllCos(cosData);
return callback();
......@@ -138,6 +135,10 @@ function renderRootComponent() {
path='domains/:id'
component={DomainDetails}
/>
<Route
path='domains/:id/mailboxes/new'
component={CreateMailBox}
/>
<Route
path='domains/:id/edit'
component={EditDomains}
......@@ -146,6 +147,14 @@ function renderRootComponent() {
path='domains/:domain_id/mailboxes'
component={Mailboxes}
/>
<Route
path='domains/:domain_id/distribution_lists/:id'
component={DistributionLists}
/>
<Route
path='domains/:domain_id/distribution_lists/:id/edit'
component={EditDistributionList}
/>
<Route
path='companies'
......
......@@ -18,4 +18,9 @@
background: $bg-warning-color;
color: $white;
}
.flash-locked {
background: $color-violet;
color: $white;
}
}
.wrapper-importer {
padding: 10px 0;
}
.col-import {
float: left;
max-height: 300px;
overflow: auto;
padding: 5px;
width: 20%;
.wrap-border-col {
&.missing-option {
border: 2px solid $red;
.list-attr {
background: $bg-import-error;
}
}
&.ok-option {
border: 2px solid $border-color-ok;
.list-attr {
background: $bg-import-ok;
}
}
.list-attr {
margin-bottom: 0;
overflow: hidden;
padding: 2px;
li {
border-top: 1px dotted $white;
overflow: hidden;
padding: 5px 0;
text-overflow: ellipsis;
&:last-child {
border-bottom: 1px dotted $white;
}
}
}
}
}
......@@ -7,10 +7,13 @@
@import 'grid';
@import 'icheck';
@import 'lists';
@import 'importer';
@import 'loader';
@import 'modal';
@import 'panel_add';
@import 'panels';
@import 'progress_bar';
@import 'progress_task';
@import 'tabs';
@import 'tooltip';
@import 'tour';
......
.new-fields {
border-top: 1px solid $color-new-fields;
margin-top: 30px;
h5 {
margin: 5px 0;
}
.new-fields-list {
.listed-field {
margin: 8px 0 0 10px;
input {
margin-right: 5px;
}
}
}
}
.actions-buttons {
margin-top: 20px;
a {
margin-left: 5px;
&:first-child {
margin-left: 0;
}
}
}
.as-table {
display: table;
.as-cell {
display: table-cell;
padding-right: 10px;
vertical-align: middle;
}
}
.margin-left {
margin-left: 5px;
}
.progress.manager-progress {
box-shadow: 0 0 5px 0 $black;
height: auto;
left: 0;
margin: auto;
position: absolute;
right: 0;
transform: translateY(-100%);
transition: transform .5s;
width: 80%;
z-index: 1;
&.active {
transform: translateY(0%);
}
&:hover {
.taskboard {
max-height: 200px;
}
}
.wrap-task {
padding: 10px 0;
}
.taskboard {
max-height: 0;
overflow: hidden;
height: 100%;
transition: max-height 1s;
.alert {
margin-bottom: 0;
}
ul {
margin: 0;
}
}
.progress-bar.manager-task-bar {
background-color: $account-link-color;
color: $white;
line-height: 1;
padding: 3px 0;
text-align: center;
width: 100%;
}
}
......@@ -614,7 +614,7 @@ a {
float: left;
height: 52px;
padding: 0;
width: 80%;
width: 70%;
.form-control {
background: none repeat scroll 0 0 $transparent;
......@@ -1272,7 +1272,7 @@ label {
.panel-body {
padding: 0;
.btn {
.btn:not(.pending_actions) {
margin: 0;
}
}
......
......@@ -54,3 +54,51 @@
}
}
}
.domain-name {
font-size: 22px;
font-weight: 600;
}
.layout-back {
overflow: hidden;
position: relative;
.hpanel {
.panel-body {
background: transparent;
border-bottom-color: transparent;
}
.panel-heading {
background: transparent;
}
}
.backstage {
height: 100%;
margin-top: -25px;
overflow: hidden;
padding: 0 15px;
position: absolute;
width: 50%;
&.back-left {
left: 0;
}
&.back-right {
right: 0;
}
.backbg {
background: $white;
border-bottom: 1px solid $color-new-fields;
border-left: 1px solid $color-new-fields;
border-right: 1px solid $color-new-fields;
height: 100%;
position: relative;
width: 100%;
}
}
}
.noplan {
background: $black;
}
.account-info {
p {
margin-bottom: 3px;
}
}
.mailbox-status {
border: solid 1px;
border-radius: 2px;
color: $white;
display: inline-block;
padding: 5px;
text-align: center;
width: 100px;
}
.m-r {
margin-right: 5px;
}
.label-locked {
background: $color-violet;
}
.passwd-field {
+ meter {
margin-top: 5px;
width: 100%;
}
}
......@@ -2,3 +2,4 @@
@import 'login';
@import 'domain';
@import 'companies';
@import 'mailbox';
......@@ -84,3 +84,12 @@ $bg-border-color: #63a758;
$account-link-color: #337ab7;
//Panel add
$color-new-fields: #e4e5e7;
// importer colors
$bg-import-ok: rgba(78, 165, 224, .2);
$border-color-ok: #4ea5e0;
$bg-import-error: #ffe8e8;
......@@ -10,6 +10,8 @@ class DomainStoreClass extends EventEmitter {
constructor() {
super();
this.current = null;
this.distributionListOwners = null;
this.distributionListMembers = null;
}
getCurrent() {
......@@ -129,6 +131,26 @@ class DomainStoreClass extends EventEmitter {
return lists;
}
getDistributionListById(listId, domain) {
if (this.current !== domain) {
this.setCurrent(domain);
}
const distributionLists = this.current.lists;
if (!distributionLists) {
return null;
}
for (const id in distributionLists) {
if (distributionLists.hasOwnProperty(id) && id === listId) {
return distributionLists[id];
}
}
return false;
}
setDistibutionLists(domain, listsArray) {
if (this.current !== domain) {
this.setCurrent(domain);
......@@ -151,6 +173,74 @@ class DomainStoreClass extends EventEmitter {
this.emitDistributionListsChange();
}
getMembers() {
if (this.distributionListMembers) {
return this.distributionListMembers;
}
return null;
}
setMembers(members) {
this.distributionListMembers = members;
}
addMember(member) {
if (this.distributionListMembers && Array.isArray(this.distributionListMembers)) {
this.distributionListMembers.push(member);
}
}
removeMember(member) {
if (this.distributionListMembers && Array.isArray(this.distributionListMembers)) {
const members = this.distributionListMembers;
const length = members.length;
for (let i = 0; i < length; i++) {
if (members[i] === member) {
members.splice(i, 1);
return true;
}
}
}
return false;
}
getOwners() {
if (this.distributionListOwners) {
return this.distributionListOwners;
}
return null;
}
setOwners(owners) {
this.distributionListOwners = owners;
}
addOwners(owner) {
if (this.distributionListOwners && Array.isArray(this.distributionListOwners)) {
this.distributionListOwners.push(owner);
}
}
removeOwner(owner) {
if (this.distributionListOwners && Array.isArray(this.distributionListOwners)) {
const owners = this.distributionListOwners;
const length = owners.length;
for (let i = 0; i < length; i++) {
if (owners[i] === owner) {
owners.splice(i, 1);
return true;
}
}
}
return false;
}
removeDistributionList(listId) {
if (this.current.lists) {
Reflect.deleteProperty(this.current.lists, listId);
......
......@@ -56,6 +56,30 @@ class EventStoreClass extends EventEmitter {
removeToastListener(callback) {
this.removeListener(eventTypes.NEW_TOAST_EVENT, callback);
}
emitTask(params) {
this.emit(eventTypes.START_TASK_LOADING_EVENT, params);
}
emitEndTask(params) {
this.emit(eventTypes.END_TASK_LOADING_EVENT, params);
}
addEndTaskSListener(params) {
this.on(eventTypes.END_TASK_LOADING_EVENT, params);
}
addTaskSListener(callback) {
this.on(eventTypes.START_TASK_LOADING_EVENT, callback);
}
removeTaskSListener(callback) {
this.removeListener(eventTypes.START_TASK_LOADING_EVENT, callback);
}
removeEndTaskSListener(params) {
this.removeListener(eventTypes.END_TASK_LOADING_EVENT, params);
}
}
var EventStore = new EventStoreClass();
......@@ -77,6 +101,12 @@ EventStore.dispatchToken = AppDispatcher.register((payload) => {
case ActionTypes.NEW_TOAST:
EventStore.emitToast(action.message);
break;
case ActionTypes.START_TASK_LOADING:
EventStore.emitTask(action.params);
break;
case ActionTypes.END_TASK_LOADING:
EventStore.emitEndTask(action.params);
break;
default:
}
});
......
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
import EventEmitter from 'events';
import Constants from '../utils/constants.jsx';
const eventTypes = Constants.EventTypes;
let mailboxesArray = null;
class MailboxStoreClass extends EventEmitter {
constructor() {
super();
this.current = null;
}
getMailboxById(id) {
if (mailboxesArray) {
const accounts = mailboxesArray.account;
const size = accounts.length;
for (let i = 0; i < size; i++) {
if (id === accounts[i].id) {
return accounts[i];
}
}
}
return false;
}
setMailbox(mailbox) {
if (mailboxesArray) {
const currentTotal = mailboxesArray.account.push(mailbox);
if (currentTotal > mailboxesArray.total) {
mailboxesArray.total = currentTotal;
}
}
}
setCurrent(account) {
this.current = account;
}
getCurrent() {
return this.current;
}
hasMailboxes() {
if (mailboxesArray) {
return true;
}
return false;
}
getMailboxes() {
return mailboxesArray;
}
setMailboxes(mailboxes) {
mailboxesArray = mailboxes;
}
changeAccount(newAccount) {
if (mailboxesArray) {
const accounts = mailboxesArray.account;
const size = accounts.length;
const id = newAccount.id;
for (let i = 0; i < size; i++) {
if (id === accounts[i].id) {
accounts[i] = newAccount;
return accounts[i];
}
}
}
return false;
}
removeAccount(account) {
if (mailboxesArray) {
const accounts = mailboxesArray.account;
const size = accounts.length;
const id = account.id;
for (let i = 0; i < size; i++) {
if (id === accounts[i].id) {
accounts.splice(i, 1);
if (mailboxesArray.total > 0) {
mailboxesArray.total = mailboxesArray.total - 1;
}
this.setMailboxes(mailboxesArray);
return true;
}
}
}
return false;
}
getAlias() {
let zimbraAlias;
if (this.current) {
if (this.current.attrs.zimbraMailAlias) {
if (!Array.isArray(this.current.attrs.zimbraMailAlias)) {
this.current.attrs.zimbraMailAlias = [this.current.attrs.zimbraMailAlias];
}
zimbraAlias = this.current.attrs.zimbraMailAlias;
} else {
this.current.attrs.zimbraMailAlias = [];
zimbraAlias = this.current.attrs.zimbraMailAlias;
}
return zimbraAlias;
}
return false;
}
setAlias(item) {
if (this.current) {
const alias = this.getAlias();
if (alias) {
alias.push(item);
}
}
}
removeAlias(alias) {
if (this.current) {
const aliasArray = this.getAlias();
if (aliasArray) {
const limit = aliasArray.length;
for (let i = 0; i < limit; i++) {
if (alias === aliasArray[i]) {
aliasArray.splice(i, 1);
return true;
}
}
}
}
return false;
}
refreshAlias(array) {
if (this.current) {
const alias = this.getAlias();
if (alias) {
Array.prototype.push.apply(alias, array);
}
return true;
}
return false;
}
// Declare here all events that fired when something happens
emitAddMassive() {
this.emit(eventTypes.MAILBOX_ADD_MASSIVE_EVENT);
}
addListenerAddMassive(callback) {
this.on(eventTypes.MAILBOX_ADD_MASSIVE_EVENT, callback);
}
removeListenerAddMassive(callback) {
this.removeListener(eventTypes.MAILBOX_ADD_MASSIVE_EVENT, callback);
}
}
const MailboxStore = new MailboxStoreClass();
export {MailboxStore as default};
// Copyright (c) 2016 ZBox, Spa. All Rights Reserved.
// See LICENSE.txt for license information.
class PaginateArrayStoreClass {
constructor(array, limit, page) {
this.getLength = this.getLength.bind(this);
this.paged = page || 1;
this.currentArray = array || [];
this.results = null;
this.limit = limit;
this.offset = ((this.paged - 1) * this.limit);
this.totalPage = Math.ceil(this.getLength() / this.limit);
}
init() {
return this.currentArray.slice(this.offset, (this.paged * this.limit));
}
nextPage() {
if ((this.paged + 1) > this.totalPage) {
return false;
}
this.paged += 1;
this.offset = ((this.paged - 1) * this.limit);
return this.currentArray.slice(this.offset, (this.paged * this.limit));
}
getLength() {
return this.currentArray.length;
}
prevPage() {
if ((this.paged - 1) < 1) {
return false;
}
this.paged -= 1;
this.offset = ((this.paged - 1) * this.limit);
return this.currentArray.slice(this.offset, (this.paged * this.limit));
}
setLimit(limit) {
this.limit = limit;
}
setArray(array) {
this.currentArray = array;
}
getResults() {
let pagination;
if (this.currentArray.length < 1) {
pagination = '0 resultados';
} else {
const start = (this.currentArray.length < 1) ? 0 : (this.offset + 1);
const end = ((this.paged * this.limit) > this.getLength()) ? this.getLength() : (this.paged * this.limit);
pagination = start + ' al ' + end + ' de ' + this.getLength();
}
return pagination;
}
reset() {
this.paged = 1;
this.offset = ((this.paged - 1) * this.limit);
this.resetTotalPage();
return this.init();
}
resetTotalPage() {
this.totalPage = Math.ceil(this.getLength() / this.limit);
}
}
//const PaginateArrayStore = new PaginateArrayStoreClass();
export {PaginateArrayStoreClass as default};
......@@ -266,6 +266,25 @@ export function modifyDomain(domain, success, error) {
);
}
export function modifyDomainByAttrs(domainId, attrs, success, error) {
initZimbra().then(
(zimbra) => {
zimbra.modifyDomain(domainId, attrs, (err, data) => {
if (err) {
const e = handleError('modifyDomainByAttrs', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('modifyDomainByAttrs', err);
return error(e);
}
);
}
export function addDistributionList(name, attrs, success, error) {
initZimbra().then(
(zimbra) => {
......@@ -361,6 +380,20 @@ export function createAccount(mail, passwd, attrs, success, error) {
);
}
export function createAccountByBatch(mail, passwd, attrs) {
return ZimbraStore.getCurrent().createAccount(mail, passwd, attrs);
}
export function modifyAccountByBatch(id, attrs) {
//window.Zimbra.modifyAccount(temp1.account[0].id, {sn: 'nuevo sn'});
return ZimbraStore.getCurrent().modifyAccount(id, attrs);
}
export function buildAccountByObject(account) {
const zimbra = ZimbraStore.getCurrent();
return zimbra.dictionary.classFactory('account', account, zimbra.client);
}
export function modifyAccount(idZimbra, attrs, success, error) {
initZimbra().then(
(zimbra) => {
......@@ -556,3 +589,79 @@ export function getAllCos(success, error) {
}
);
}
export function getAllDistributionLists(query, success, error) {
initZimbra().then(
(zimbra) => {
zimbra.getAllDistributionLists((err, data) => {
if (err) {
const e = handleError('getAllDistributionLists', err);
return error(e);
}
return success(data);
}, query);
},
(err) => {
const e = handleError('getAllDistributionLists', err);
return error(e);
}
);
}
export function getDistributionList(id, success, error) {
initZimbra().then(
(zimbra) => {
zimbra.getDistributionList(id, (err, data) => {
if (err) {
const e = handleError('getDistributionList', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('getDistributionList', err);
return error(e);
}
);
}
export function modifyDistributionList(id, attrs, success, error) {
initZimbra().then(
(zimbra) => {
zimbra.modifyDistributionList(id, attrs, (err, data) => {
if (err) {
const e = handleError('modifyDistributionList', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('modifyDistributionList', err);
return error(e);
}
);
}
export function renameAccount(account, success, error) {
initZimbra().then(
(zimbra) => {
zimbra.renameAccount(account, (err, data) => {
if (err) {
const e = handleError('renameAccount', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('renameAccount', err);
return error(e);
}
);
}
......@@ -7,6 +7,8 @@ export default {
ActionTypes: keyMirror({
START_LOADING: null,
END_LOADING: null,
START_TASK_LOADING: null,
END_TASK_LOADING: null,
USER_CHANGED: null,
RECEIVED_ERROR: null,
NEW_MESSAGE: null,
......@@ -21,18 +23,23 @@ export default {
EventTypes: keyMirror({
DOMAIN_ADMINS_CHANGE_EVENT: null,
DOMAIN_DLS_CHANGE_EVENT: null,
ACCOUNT_CHANGE_EVENT: null,
START_LOADING_EVENT: null,
END_LOADING_EVENT: null,
START_TASK_LOADING_EVENT: null,
END_TASK_LOADING_EVENT: null,
USER_CHANGE_EVENT: null,
NEW_MESSAGE_EVENT: null,
NEW_TOAST_EVENT: null
NEW_TOAST_EVENT: null,
MAILBOX_ADD_MASSIVE_EVENT: null
}),
MessageType: keyMirror({
SUCCESS: null,
WARNING: null,
ERROR: null,
INFO: null
INFO: null,
LOCKED: null
}),
ZimbraCodes: {
......
......@@ -311,3 +311,236 @@ export function getEnabledPlansByCosId(cosArray) {
return plans;
}
export function getMembers(members, label) {
let tag = label;
if (Array.isArray(members)) {
if (members.length === 1) {
tag = tag.slice(0, -1);
}
return members.length + ' ' + tag;
}
throw new Error('El tipo de members no es un arreglo : ' + typeof members);
}
export function bytesToMegas(bytes) {
let size = '0 MB';
if (bytes && typeof bytes === 'number') {
size = bytes;
const mb = 1024;
size = ((size / mb) / mb).toFixed(2) + 'MB';
}
return size;
}
export function getDomainFromString(string, otherwise) {
let domain = otherwise || 'Dominio Invalido';
if (typeof string === 'string' && string.lastIndexOf('@') > -1) {
domain = string.split('@');
domain = domain.pop();
}
return domain;
}
export function exportAsCSV(data, title, hasLabel) {
const info = (typeof data === 'object') ? data : JSON.parse(data);
let CSV = '';
CSV += title + '\r\n\n';
if (hasLabel) {
let row = '';
for (const index in info[0]) {
if (info[0].hasOwnProperty(index)) {
row += index + ',';
}
}
row = row.slice(0, row.length - 1);
CSV += row + '\r\n';
}
for (var i = 0; i < info.length; i++) {
let row = '';
for (var index in info[i]) {
if (info[i].hasOwnProperty(index)) {
row += '\'' + info[i][index] + '\',';
}
}
row = row.slice(0, -1);
CSV += row + '\r\n';
}
if (CSV === '') {
return;
}
const fileName = title.replace(/ /g, '_') + new Date().getTime();
const uri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(CSV);
if (isSafari()) {
const time = 500;
var close = window.open('data:attachment/csv;charset=utf-8,' + encodeURIComponent(CSV), '_blank', 'width=1,height=1');
setTimeout(() => {
close.close();
}, time);
return;
}
const link = document.createElement('a');
link.href = uri;
link.style = 'visibility:hidden';
link.download = fileName + '.csv';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
export function isChrome() {
if (navigator.userAgent.indexOf('Chrome') > -1) {
return true;
}
return false;
}
export function isSafari() {
if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) {
return true;
}
return false;
}
export function isIosChrome() {
// https://developer.chrome.com/multidevice/user-agent
return navigator.userAgent.indexOf('CriOS') !== -1;
}
export function isFirefox() {
return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
}
export function isIE() {
if (window.navigator && window.navigator.userAgent) {
var ua = window.navigator.userAgent;
return ua.indexOf('Trident/7.0') > 0 || ua.indexOf('Trident/6.0') > 0;
}
return false;
}
export function isEdge() {
return window.navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1;
}
export function addOrRemoveAlias(account, params) {
let promises = [];
for (const label in params) {
if (params.hasOwnProperty(label)) {
if (label === 'add') {
const length = params[label].length;
for (let i = 0; i < length; i++) {
let mypromise = new Promise((resolve) => {
account.addAccountAlias(params[label][i], (response) => {
if (response) {
resolve({
resolved: false,
err: response,
item: params[label][i],
action: 'add'
});
} else {
resolve({
resolved: true,
action: 'add'
});
}
});
});
promises.push(mypromise);
}
}
if (label === 'remove') {
const length = params[label].length;
for (let i = 0; i < length; i++) {
let mypromise = new Promise((resolve) => {
account.removeAccountAlias(params[label][i], (response) => {
if (response) {
resolve({
resolved: false,
err: response,
item: params[label][i],
action: 'remove'
});
} else {
resolve({
resolved: true,
action: 'remove'
});
}
});
});
promises.push(mypromise);
}
}
}
}
return Promise.all(promises);
}
export function getEnabledPlansObjectByCos(cosArray, cosID) {
const configPlans = global.window.manager_config.plans;
const plans = {};
const id = cosID || false;
cosArray.forEach((cos) => {
const key = cos.name;
if (configPlans.hasOwnProperty(key) && configPlans[key]) {
if (id) {
if (cos.id === id) {
plans.name = key;
plans.id = cos.id;
plans.attrs = cos.attrs;
}
} else {
plans[key] = {};
plans[key].id = cos.id;
plans[key].attrs = cos.attrs;
}
}
});
return plans;
}
export function getOwners(owners) {
if (Array.isArray(owners)) {
const limit = owners.length;
const ownersArray = [];
for (let i = 0; i < limit; i++) {
ownersArray.push(owners[i].name);
}
return ownersArray;
}
throw Error('Owners array no es un arreglo :' + typeof owners);
}
......@@ -125,6 +125,13 @@ if (!DEV) {
config.plugins.push(
new webpack.optimize.DedupePlugin()
);
config.plugins.push(
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
);
}
module.exports = config;
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