Commit 91a2ec23 authored by Patricio Bruna's avatar Patricio Bruna

Merge pull request #42 from ZBoxApp/distribution_lists

apply store in distribution list and mailbox, and add, edit, remove, distribution list, add pagination mailbox
parents 33df3db4 f190b8af
This diff is collapsed.
import $ from 'jquery';
import React from 'react';
import Button from '../button.jsx';
import MessageBar from '../message_bar.jsx';
import Panel from '../panel.jsx';
import * as Client from '../../utils/client.jsx';
import * as GlobalActions from '../../action_creators/global_actions.jsx';
import * as Utils from '../../utils/utils.jsx';
import EventStore from '../../stores/event_store.jsx';
import Promise from 'bluebird';
import Constants from '../../utils/constants.jsx';
const messageType = Constants.MessageType;
export default class EditDistributionList extends React.Component {
constructor(props) {
super(props);
this.showMessage = this.showMessage.bind(this);
this.saveChanges = this.saveChanges.bind(this);
this.state = {};
}
showMessage(attrs) {
this.setState({
error: attrs.error,
type: attrs.typeError
});
}
saveChanges(e) {
e.preventDefault();
Utils.toggleStatusButtons('.action-edit-dl', true);
Utils.validateInputRequired(this.refs).then(() => {
const id = this.props.params.id;
const attrs = {};
const mail = this.refs.mail.value;
attrs.displayName = this.refs.displayName.value;
Client.modifyDistributionList(id, attrs, (dl) => {
const domain = dl.name.split('@').pop();
const newNameDL = `${mail}@${domain}`;
dl.rename(newNameDL, (err) => {
Utils.toggleStatusButtons('.action-edit-dl', false);
if (err) {
return GlobalActions.emitMessage({
error: err.extra.reason,
typeError: messageType.ERROR
});
}
return GlobalActions.emitMessage({
error: 'Se han actualizado sus datos éxitosamente.',
typeError: messageType.SUCCESS
});
});
}, (err) => {
GlobalActions.emitMessage({
error: err.message,
typeError: messageType.ERROR
});
Utils.toggleStatusButtons('.action-edit-dl', false);
});
}).catch((error) => {
GlobalActions.emitMessage({
error: error.message,
type: messageType.ERROR
});
error.node.focus();
Utils.toggleStatusButtons('.action-edit-dl', false);
});
}
getDistributionList() {
const id = this.props.params.id;
new Promise((resolve, reject) => {
Client.getDistributionList(id, (success) => {
resolve(success);
}, (error) => {
reject(error);
});
}).then((res) => {
const domain = this.refs.domain;
const displayName = this.refs.displayName;
const mail = this.refs.mail;
const dl = res.name.split('@');
domain.innerHTML = dl.pop();
if (res.attrs.displayName) {
displayName.value = res.attrs.displayName;
}
mail.value = dl.shift();
}).catch((err) => {
GlobalActions.emitMessage({
error: err.message,
type: messageType.ERROR
});
Utils.toggleStatusButtons('.action-edit-dl', true);
}).finally(() => {
GlobalActions.emitEndLoading();
});
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
GlobalActions.emitEndLoading();
$('#sidebar-domain').removeClass('active');
this.getDistributionList();
}
componentWillUnmount() {
EventStore.removeMessageListener(this.showMessage);
$('#sidebar-domain').removeClass('active');
}
render() {
let message;
let form;
let actions;
if (this.state.error) {
message = (
<MessageBar
message={this.state.error}
type={this.state.type}
autoclose={true}
/>
);
}
form = (
<form
className='simple_form form-horizontal mailbox-form'
id='editAccount'
>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
<abbr title='requerido'>{'*'}</abbr>
{'Dirección'}
</label>
<div className='col-sm-8'>
<div className='input-group'>
<input
type='text'
className='form-control'
ref='mail'
data-required='true'
data-message='La dirección de correo es requerida, por favor verifique.'
placeholder='Dirección'
/>
<span
className='input-group-addon'
ref='domain'
>
</span>
</div>
</div>
</div>
<div className='form-group string'>
<label className='string required col-sm-3 control-label'>
{'Nombre'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
ref='displayName'
placeholder='Nombre mostrado en contactos'
/>
</div>
</div>
<div className='form-group'>
<div className='col-sm-3'></div>
<div className='col-sm-8'>
<Button
btnAttrs={
{
className: 'btn btn-info action-edit-dl',
onClick: (e) => {
this.saveChanges(e);
}
}
}
>
{'Guardar'}
</Button>
<Button
btnAttrs={
{
className: 'btn btn-default action-button',
onClick: (e) => {
Utils.handleLink(e, `/domains/${this.props.params.domain_id}/distribution_lists/${this.props.params.id}`);
}
}
}
>
{'Cancelar'}
</Button>
</div>
</div>
</form>
);
actions = [
{
label: 'Guardar',
props: {
className: 'btn btn-info btn-xs action-edit-dl',
onClick: (e) => {
this.saveChanges(e);
}
}
},
{
label: 'Cancelar',
props: {
className: 'btn btn-default btn-xs action-button',
onClick: (e) => {
Utils.handleLink(e, `/domains/${this.props.params.domain_id}/distribution_lists/${this.props.params.id}`);
}
}
}
];
return (
<div>
<div className='content animate-panel'>
{message}
<div className='row'>
<div className='col-md-12 central-content'>
<Panel
title={'Editar Lista de Distribución'}
btnsHeader={actions}
classHeader={'forum-box'}
>
{form}
</Panel>
</div>
</div>
</div>
</div>
);
}
}
EditDistributionList.propTypes = {
location: React.PropTypes.object,
params: React.PropTypes.any
};
......@@ -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>
......
......@@ -36,7 +36,10 @@ export default class ConfirmDeleteModal extends React.Component {
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,
......
......@@ -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'}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -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>
);
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -7,7 +7,23 @@ 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() {
......@@ -16,24 +32,32 @@ export default class BlockGeneralInfoMailbox extends React.Component {
switch (this.props.data.attrs.zimbraAccountStatus) {
case 'inactive':
this.status = 'Desactivada';
this.className = 'btn btn-md btn-default';
this.className = 'btn-default mailbox-status';
break;
case 'locked':
this.status = 'Bloqueada';
this.className = 'btn btn-md btn-primary2';
this.className = 'btn-primary2 mailbox-status';
break;
default:
this.status = 'Activa';
this.className = 'btn btn-md btn-info';
this.className = 'btn-info mailbox-status';
break;
}
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,7 +83,7 @@ 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>
......
......@@ -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}
......
This diff is collapsed.
This diff is collapsed.
{
"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",
......
......@@ -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();
......@@ -146,6 +143,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;
}
}
......@@ -9,6 +9,7 @@
@import 'lists';
@import 'loader';
@import 'modal';
@import 'panel_add';
@import 'panels';
@import 'progress_bar';
@import 'tabs';
......
.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;
}
......@@ -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,8 @@
}
}
}
.domain-name {
font-size: 22px;
font-weight: 600;
}
.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;
}
......@@ -2,3 +2,4 @@
@import 'login';
@import 'domain';
@import 'companies';
@import 'mailbox';
......@@ -3,7 +3,6 @@
// Dependancies
@import '~bootstrap/dist/css/bootstrap.css';
@import '~jasny-bootstrap/dist/css/jasny-bootstrap.css';
@import '~perfect-scrollbar/dist/css/perfect-scrollbar.css';
@import '~font-awesome/css/font-awesome.css';
@import '~nprogress/nprogress.css';
@import '~toastr/build/toastr.css';
......
......@@ -84,3 +84,6 @@ $bg-border-color: #63a758;
$account-link-color: #337ab7;
//Panel add
$color-new-fields: #e4e5e7;
......@@ -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);
......
This diff is collapsed.
// 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};
This diff is collapsed.
......@@ -21,6 +21,7 @@ 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,
USER_CHANGE_EVENT: null,
......@@ -32,7 +33,8 @@ export default {
SUCCESS: null,
WARNING: null,
ERROR: null,
INFO: null
INFO: null,
LOCKED: null
}),
ZimbraCodes: {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment