2565-address_fixtures #440
|
@ -9,7 +9,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"id": true
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -56,7 +56,8 @@
|
||||||
"roles": {
|
"roles": {
|
||||||
"type": "hasMany",
|
"type": "hasMany",
|
||||||
"model": "RoleRole",
|
"model": "RoleRole",
|
||||||
"foreignKey": "role"
|
"foreignKey": "role",
|
||||||
|
"primaryKey": "roleFk"
|
||||||
},
|
},
|
||||||
"emailUser": {
|
"emailUser": {
|
||||||
"type": "hasOne",
|
"type": "hasOne",
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE `account`.`roleRole`
|
||||||
|
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
UPDATE `account`.`role` SET id = 100 WHERE `name` = 'root';
|
||||||
|
|
||||||
|
CALL account.role_sync;
|
|
@ -0,0 +1,24 @@
|
||||||
|
CREATE TABLE `vn`.`supplierContact` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`supplierFk` INT(11) NULL DEFAULT NULL,
|
||||||
|
`phone` VARCHAR(16) NULL DEFAULT NULL,
|
||||||
|
`mobile` VARCHAR(16) NULL DEFAULT NULL,
|
||||||
|
`email` VARCHAR(255) NULL DEFAULT NULL,
|
||||||
|
`observation` TEXT NULL DEFAULT NULL,
|
||||||
|
`name` VARCHAR(255) NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`))
|
||||||
|
ENGINE = InnoDB;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`supplierContact`
|
||||||
|
ADD CONSTRAINT `supplier_id`
|
||||||
|
FOREIGN KEY (`supplierFk`)
|
||||||
|
REFERENCES `vn`.`supplier` (`id`)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
INSERT INTO vn.supplierContact(supplierFk,phone,mobile,email,observation,`name`)
|
||||||
|
SELECT r.Id_Proveedor,c.Telefono,c.Movil,c.email,c.Notas,concat(c.Nombre," ", IFNULL(c.Apellidos,""))
|
||||||
|
FROM vn2008.Contactos c
|
||||||
|
JOIN vn2008.Relaciones r ON r.Id_Contacto = c.Id_Contacto
|
||||||
|
JOIN vn.supplier s ON s.id = r.Id_Proveedor;
|
|
@ -0,0 +1,2 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('supplier', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('SupplierContact', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
|
|
@ -1217,6 +1217,13 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
|
||||||
(2, 'Farmer King', 'The farmer', 4000020002, 1, 'B22222222', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 8),
|
(2, 'Farmer King', 'The farmer', 4000020002, 1, 'B22222222', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 8),
|
||||||
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, 'C33333333', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, NULL, NULL);
|
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, 'C33333333', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, NULL, NULL);
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
|
||||||
|
VALUES
|
||||||
|
(1, 1, 123121212, 654789123, 'supplier1@email.es', 'observation1', 'the boss'),
|
||||||
|
(2, 1, 987456132, NULL, NULL, NULL, 'the salesperson'),
|
||||||
|
(3, 2, 321654987, NULL, 'supplier2@email.es', NULL, NULL),
|
||||||
|
(4, 442, 321654987, NULL, NULL, 'observation442', NULL);
|
||||||
|
|
||||||
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
|
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 2, 'available', CONCAT_WS('/',1,CURDATE()), CURRENT_TIMESTAMP(), DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL 15 MINUTE), CURDATE(), NULL),
|
(1, 2, 'available', CONCAT_WS('/',1,CURDATE()), CURRENT_TIMESTAMP(), DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL 15 MINUTE), CURDATE(), NULL),
|
||||||
|
|
|
@ -912,5 +912,16 @@ export default {
|
||||||
alias: 'vn-supplier-descriptor vn-label-value[label="Alias"]',
|
alias: 'vn-supplier-descriptor vn-label-value[label="Alias"]',
|
||||||
clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]',
|
clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]',
|
||||||
entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]',
|
entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]',
|
||||||
|
},
|
||||||
|
supplierContact: {
|
||||||
|
anyContact: 'vn-supplier-contact > form > vn-card > div',
|
||||||
|
addNewContact: 'vn-supplier-contact vn-icon[icon="add_circle"]',
|
||||||
|
thirdContactName: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.name"]',
|
||||||
|
thirdContactPhone: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.phone"]',
|
||||||
|
thirdContactMobile: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.mobile"]',
|
||||||
|
thirdContactEmail: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.email"]',
|
||||||
|
thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]',
|
||||||
|
saveButton: 'vn-supplier-contact button[type="submit"]',
|
||||||
|
thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
import selectors from '../../helpers/selectors.js';
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
describe('Supplier descriptor path', () => {
|
describe('Supplier summary & descriptor path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import selectors from '../../helpers/selectors.js';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Supplier contact path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('administrative', 'supplier');
|
||||||
|
await page.accessToSearchResult('1');
|
||||||
|
await page.accessToSection('supplier.card.contact');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new contact', async() => {
|
||||||
|
await page.waitToClick(selectors.supplierContact.addNewContact);
|
||||||
|
await page.write(selectors.supplierContact.thirdContactName, 'The tester');
|
||||||
|
await page.write(selectors.supplierContact.thirdContactPhone, '99 999 99 99');
|
||||||
|
await page.write(selectors.supplierContact.thirdContactMobile, '555 55 55 55');
|
||||||
|
await page.write(selectors.supplierContact.thirdContactEmail, 'testing@puppeteer.com');
|
||||||
|
await page.write(selectors.supplierContact.thirdContactNotes, 'the end to end integration tester');
|
||||||
|
await page.waitToClick(selectors.supplierContact.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toBe('Data saved!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should reload the section and count the contacts`, async() => {
|
||||||
|
await page.reloadSection('supplier.card.contact');
|
||||||
|
const result = await page.countElement(selectors.supplierContact.anyContact);
|
||||||
|
|
||||||
|
expect(result).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should check the new contact name was saved correctly`, async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactName, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain('The tester');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should check the new contact phone was saved correctly`, async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactPhone, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain('99 999 99 99');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should check the new contact mobile was saved correctly`, async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactMobile, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain('555 55 55 55');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should check the new contact email was saved correctly`, async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactEmail, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain('testing@puppeteer.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should check the new contact note was saved correctly`, async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactNotes, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain('the end to end integration tester');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should remove the created contact`, async() => {
|
||||||
|
await page.waitToClick(selectors.supplierContact.thirdContactDeleteButton, 'value');
|
||||||
|
await page.waitToClick(selectors.supplierContact.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toBe('Data saved!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should reload the section and count the amount of contacts went back to 2`, async() => {
|
||||||
|
await page.reloadSection('supplier.card.contact');
|
||||||
|
const result = await page.countElement(selectors.supplierContact.anyContact);
|
||||||
|
|
||||||
|
expect(result).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -41,7 +41,7 @@ module.exports = Self => {
|
||||||
attributes: ['dn'],
|
attributes: ['dn'],
|
||||||
filter: 'objectClass=posixGroup'
|
filter: 'objectClass=posixGroup'
|
||||||
};
|
};
|
||||||
res = await client.search(ldapConfig.groupDn, opts);
|
let res = await client.search(ldapConfig.groupDn, opts);
|
||||||
|
|
||||||
let reqs = [];
|
let reqs = [];
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
@ -62,31 +62,28 @@ module.exports = Self => {
|
||||||
let roles = await $.Role.find({
|
let roles = await $.Role.find({
|
||||||
fields: ['id', 'name']
|
fields: ['id', 'name']
|
||||||
});
|
});
|
||||||
|
let roleRoles = await $.RoleRole.find({
|
||||||
|
fields: ['role', 'inheritsFrom']
|
||||||
|
});
|
||||||
|
let roleMap = toMap(roleRoles, e => {
|
||||||
|
return {key: e.inheritsFrom, val: e.role};
|
||||||
|
});
|
||||||
|
|
||||||
let accounts = await $.UserAccount.find({
|
let accounts = await $.UserAccount.find({
|
||||||
fields: ['id'],
|
fields: ['id'],
|
||||||
include: {
|
include: {
|
||||||
relation: 'user',
|
relation: 'user',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['name'],
|
fields: ['name', 'roleFk'],
|
||||||
include: {
|
where: {active: true}
|
||||||
relation: 'roles',
|
|
||||||
scope: {
|
|
||||||
fields: ['inheritsFrom']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let accountMap = toMap(accounts, e => {
|
||||||
let map = new Map();
|
let user = e.user();
|
||||||
for (let account of accounts) {
|
if (!user) return;
|
||||||
let user = account.user();
|
return {key: user.roleFk, val: user.name};
|
||||||
for (let inherit of user.roles()) {
|
});
|
||||||
let roleId = inherit.inheritsFrom;
|
|
||||||
if (!map.has(roleId)) map.set(roleId, []);
|
|
||||||
map.get(roleId).push(user.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reqs = [];
|
reqs = [];
|
||||||
for (let role of roles) {
|
for (let role of roles) {
|
||||||
|
@ -96,8 +93,14 @@ module.exports = Self => {
|
||||||
gidNumber: accountConfig.idBase + role.id
|
gidNumber: accountConfig.idBase + role.id
|
||||||
};
|
};
|
||||||
|
|
||||||
let memberUid = map.get(role.id);
|
let memberUid = [];
|
||||||
if (memberUid) newEntry.memberUid = memberUid;
|
for (subrole of roleMap.get(role.id) || [])
|
||||||
|
memberUid = memberUid.concat(accountMap.get(subrole) || []);
|
||||||
|
|
||||||
|
if (memberUid.length) {
|
||||||
|
memberUid.sort((a, b) => a.localeCompare(b));
|
||||||
|
newEntry.memberUid = memberUid;
|
||||||
|
}
|
||||||
|
|
||||||
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
||||||
reqs.push(client.add(dn, newEntry));
|
reqs.push(client.add(dn, newEntry));
|
||||||
|
@ -107,8 +110,19 @@ module.exports = Self => {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Cannot disconnect, hangs on undind() call
|
await client.unbind();
|
||||||
// await client.unbind();
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function toMap(array, fn) {
|
||||||
|
let map = new Map();
|
||||||
|
for (let item of array) {
|
||||||
|
let keyVal = fn(item);
|
||||||
|
if (!keyVal) continue;
|
||||||
|
let key = keyVal.key;
|
||||||
|
if (!map.has(key)) map.set(key, []);
|
||||||
|
map.get(key).push(keyVal.val);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
const SyncEngine = require('../../util/sync-engine');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('syncAll', {
|
||||||
|
description: 'Synchronizes user database with LDAP and Samba',
|
||||||
|
http: {
|
||||||
|
path: `/syncAll`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.syncAll = async function() {
|
||||||
|
let $ = Self.app.models;
|
||||||
|
|
||||||
|
let se = new SyncEngine();
|
||||||
|
await se.init($);
|
||||||
|
|
||||||
|
let usersToSync = await se.getUsers();
|
||||||
|
usersToSync = Array.from(usersToSync.values())
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
for (let user of usersToSync) {
|
||||||
|
try {
|
||||||
|
console.log(`Synchronizing user '${user}'`);
|
||||||
|
await se.sync(user);
|
||||||
|
console.log(` -> '${user}' sinchronized`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(` -> '${user}' synchronization error:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await se.deinit();
|
||||||
|
|
||||||
|
await $.RoleInherit.sync();
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,7 +1,5 @@
|
||||||
const ldap = require('../../util/ldapjs-extra');
|
|
||||||
const nthash = require('smbhash').nthash;
|
const SyncEngine = require('../../util/sync-engine');
|
||||||
const ssh = require('node-ssh');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('sync', {
|
Self.remoteMethod('sync', {
|
||||||
|
@ -19,7 +17,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
http: {
|
http: {
|
||||||
path: `/sync`,
|
path: `/:userName/sync`,
|
||||||
verb: 'PATCH'
|
verb: 'PATCH'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -35,233 +33,19 @@ module.exports = Self => {
|
||||||
|
|
||||||
if (user && isSync) return;
|
if (user && isSync) return;
|
||||||
|
|
||||||
let accountConfig;
|
|
||||||
let mailConfig;
|
|
||||||
let extraParams;
|
|
||||||
let hasAccount = false;
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
accountConfig = await $.AccountConfig.findOne({
|
|
||||||
fields: ['homedir', 'shell', 'idBase']
|
|
||||||
});
|
|
||||||
mailConfig = await $.MailConfig.findOne({
|
|
||||||
fields: ['domain']
|
|
||||||
});
|
|
||||||
|
|
||||||
user = await $.Account.findById(user.id, {
|
|
||||||
fields: [
|
|
||||||
'id',
|
|
||||||
'nickname',
|
|
||||||
'email',
|
|
||||||
'lang',
|
|
||||||
'roleFk',
|
|
||||||
'sync',
|
|
||||||
'active',
|
|
||||||
'created',
|
|
||||||
'updated'
|
|
||||||
],
|
|
||||||
where: {name: userName},
|
|
||||||
include: {
|
|
||||||
relation: 'roles',
|
|
||||||
scope: {
|
|
||||||
include: {
|
|
||||||
relation: 'inherits',
|
|
||||||
scope: {
|
|
||||||
fields: ['name']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
extraParams = {
|
|
||||||
corporateMail: `${userName}@${mailConfig.domain}`,
|
|
||||||
uidNumber: accountConfig.idBase + user.id
|
|
||||||
};
|
|
||||||
|
|
||||||
hasAccount = user.active
|
|
||||||
&& await $.UserAccount.exists(user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
let bcryptPassword = $.User.hashPassword(password);
|
|
||||||
|
|
||||||
await $.Account.upsertWithWhere({id: user.id},
|
|
||||||
{bcryptPassword}
|
|
||||||
);
|
|
||||||
await $.user.upsert({
|
|
||||||
id: user.id,
|
|
||||||
username: userName,
|
|
||||||
password: bcryptPassword,
|
|
||||||
email: user.email,
|
|
||||||
created: user.created,
|
|
||||||
updated: user.updated
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIP
|
|
||||||
|
|
||||||
if (hasAccount) {
|
|
||||||
await Self.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
|
||||||
[user.id, password]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDAP
|
|
||||||
|
|
||||||
let ldapConfig = await $.LdapConfig.findOne({
|
|
||||||
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ldapConfig) {
|
|
||||||
let ldapClient = ldap.createClient({
|
|
||||||
url: `ldap://${ldapConfig.host}:389`
|
|
||||||
});
|
|
||||||
|
|
||||||
let ldapPassword = Buffer
|
|
||||||
.from(ldapConfig.password, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
|
|
||||||
|
|
||||||
let err;
|
let err;
|
||||||
try {
|
let se = new SyncEngine();
|
||||||
// Deletes user
|
await se.init($);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
await se.sync(userName, password, true);
|
||||||
await ldapClient.del(dn);
|
await $.UserSync.destroyById(userName);
|
||||||
} catch (e) {
|
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes user from groups
|
|
||||||
|
|
||||||
let opts = {
|
|
||||||
scope: 'sub',
|
|
||||||
attributes: ['dn'],
|
|
||||||
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
|
||||||
};
|
|
||||||
res = await ldapClient.search(ldapConfig.groupDn, opts);
|
|
||||||
|
|
||||||
let oldGroups = [];
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
res.on('error', reject);
|
|
||||||
res.on('searchEntry', e => oldGroups.push(e.object));
|
|
||||||
res.on('end', resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
let reqs = [];
|
|
||||||
for (oldGroup of oldGroups) {
|
|
||||||
let change = new ldap.Change({
|
|
||||||
operation: 'delete',
|
|
||||||
modification: {memberUid: userName}
|
|
||||||
});
|
|
||||||
reqs.push(ldapClient.modify(oldGroup.dn, change));
|
|
||||||
}
|
|
||||||
await Promise.all(reqs);
|
|
||||||
|
|
||||||
if (hasAccount) {
|
|
||||||
// Recreates user
|
|
||||||
|
|
||||||
let nameArgs = user.nickname.split(' ');
|
|
||||||
let sshaPassword = crypto
|
|
||||||
.createHash('sha1')
|
|
||||||
.update(password)
|
|
||||||
.digest('base64');
|
|
||||||
|
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
|
||||||
let newEntry = {
|
|
||||||
uid: userName,
|
|
||||||
objectClass: [
|
|
||||||
'inetOrgPerson',
|
|
||||||
'posixAccount',
|
|
||||||
'sambaSamAccount'
|
|
||||||
],
|
|
||||||
cn: user.nickname || userName,
|
|
||||||
displayName: user.nickname,
|
|
||||||
givenName: nameArgs[0],
|
|
||||||
sn: nameArgs[1] || 'Empty',
|
|
||||||
mail: extraParams.corporateMail,
|
|
||||||
userPassword: `{SSHA}${sshaPassword}`,
|
|
||||||
preferredLanguage: user.lang,
|
|
||||||
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
|
||||||
loginShell: accountConfig.shell,
|
|
||||||
uidNumber: extraParams.uidNumber,
|
|
||||||
gidNumber: accountConfig.idBase + user.roleFk,
|
|
||||||
sambaSID: '-',
|
|
||||||
sambaNTPassword: nthash(password)
|
|
||||||
};
|
|
||||||
await ldapClient.add(dn, newEntry);
|
|
||||||
|
|
||||||
// Adds user to groups
|
|
||||||
|
|
||||||
let reqs = [];
|
|
||||||
for (let role of user.roles()) {
|
|
||||||
let change = new ldap.Change({
|
|
||||||
operation: 'add',
|
|
||||||
modification: {memberUid: userName}
|
|
||||||
});
|
|
||||||
let roleName = role.inherits().name;
|
|
||||||
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
|
|
||||||
reqs.push(ldapClient.modify(dn, change));
|
|
||||||
}
|
|
||||||
await Promise.all(reqs);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Cannot disconnect, hangs on undind() call
|
await se.deinit();
|
||||||
// await ldapClient.unbind();
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
}
|
|
||||||
|
|
||||||
// Samba
|
|
||||||
|
|
||||||
let sambaConfig = await $.SambaConfig.findOne({
|
|
||||||
fields: ['host', 'sshUser', 'sshPass']
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sambaConfig) {
|
|
||||||
let sshPassword = Buffer
|
|
||||||
.from(sambaConfig.sshPass, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
|
|
||||||
let sshClient = new ssh.NodeSSH();
|
|
||||||
await sshClient.connect({
|
|
||||||
host: sambaConfig.host,
|
|
||||||
username: sambaConfig.sshUser,
|
|
||||||
password: sshPassword
|
|
||||||
});
|
|
||||||
|
|
||||||
let commands;
|
|
||||||
|
|
||||||
if (hasAccount) {
|
|
||||||
commands = [
|
|
||||||
`samba-tool user create "${userName}" `
|
|
||||||
+ `--uid-number=${extraParams.uidNumber} `
|
|
||||||
+ `--mail-address="${extraParams.corporateMail}" `
|
|
||||||
+ `--random-password`,
|
|
||||||
`samba-tool user setexpiry "${userName}" `
|
|
||||||
+ `--noexpiry`,
|
|
||||||
`samba-tool user setpassword "${userName}" `
|
|
||||||
+ `--newpassword="${password}"`,
|
|
||||||
`mkhomedir_helper "${userName}" 0027`
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
commands = [
|
|
||||||
`samba-tool user delete "${userName}"`
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let command of commands)
|
|
||||||
await sshClient.execCommand(command);
|
|
||||||
|
|
||||||
await sshClient.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as synchronized
|
|
||||||
|
|
||||||
await $.UserSync.destroyById(userName);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
"LdapConfig": {
|
"LdapConfig": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"Mail": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"MailAlias": {
|
"MailAlias": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
@ -34,8 +37,5 @@
|
||||||
},
|
},
|
||||||
"UserSync": {
|
"UserSync": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
|
||||||
"Mail": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"role": {
|
"id": {
|
||||||
"id": true
|
"id": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/user-account/sync')(Self);
|
require('../methods/user-account/sync')(Self);
|
||||||
require('../methods/user-account/sync-by-id')(Self);
|
require('../methods/user-account/sync-by-id')(Self);
|
||||||
|
require('../methods/user-account/sync-all')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Base class for user synchronizators.
|
||||||
|
*
|
||||||
|
* @property {Array<Model>} $
|
||||||
|
* @property {Object} accountConfig
|
||||||
|
* @property {Object} mailConfig
|
||||||
|
*/
|
||||||
|
class SyncConnector {
|
||||||
|
/**
|
||||||
|
* Initalizes the connector.
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get users to synchronize.
|
||||||
|
*
|
||||||
|
* @param {Set} usersToSync Set where users are added
|
||||||
|
*/
|
||||||
|
async getUsers(usersToSync) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes a user.
|
||||||
|
*
|
||||||
|
* @param {Object} info User information
|
||||||
|
* @param {String} userName The user name
|
||||||
|
* @param {String} password Thepassword
|
||||||
|
*/
|
||||||
|
async sync(info, userName, password) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes user groups.
|
||||||
|
*
|
||||||
|
* @param {User} user Instace of user
|
||||||
|
* @param {String} userName The user name
|
||||||
|
*/
|
||||||
|
async syncGroups(user, userName) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinitalizes the connector.
|
||||||
|
*/
|
||||||
|
async deinit() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncConnector.connectors = [];
|
||||||
|
module.exports = SyncConnector;
|
|
@ -0,0 +1,57 @@
|
||||||
|
|
||||||
|
const SyncConnector = require('./sync-connector');
|
||||||
|
|
||||||
|
class SyncDb extends SyncConnector {
|
||||||
|
async sync(info, userName, password) {
|
||||||
|
let {$} = this;
|
||||||
|
let {user} = info;
|
||||||
|
|
||||||
|
if (user && user.active) {
|
||||||
|
let bcryptPassword = password
|
||||||
|
? $.User.hashPassword(password)
|
||||||
|
: user.bcryptPassword;
|
||||||
|
|
||||||
|
await $.Account.upsertWithWhere({id: user.id},
|
||||||
|
{bcryptPassword}
|
||||||
|
);
|
||||||
|
|
||||||
|
let dbUser = {
|
||||||
|
id: user.id,
|
||||||
|
username: userName,
|
||||||
|
email: user.email,
|
||||||
|
created: user.created,
|
||||||
|
updated: user.updated
|
||||||
|
};
|
||||||
|
if (bcryptPassword)
|
||||||
|
dbUser.password = bcryptPassword;
|
||||||
|
|
||||||
|
if (await $.user.exists(user.id))
|
||||||
|
await $.user.replaceById(user.id, dbUser);
|
||||||
|
else
|
||||||
|
await $.user.create(dbUser);
|
||||||
|
} else
|
||||||
|
await $.user.destroyAll({username: userName});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers(usersToSync) {
|
||||||
|
let accounts = await this.$.UserAccount.find({
|
||||||
|
fields: ['id'],
|
||||||
|
include: {
|
||||||
|
relation: 'user',
|
||||||
|
scope: {
|
||||||
|
fields: ['name'],
|
||||||
|
where: {active: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let account of accounts) {
|
||||||
|
let user = account.user();
|
||||||
|
if (!user) continue;
|
||||||
|
usersToSync.add(user.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncConnector.connectors.push(SyncDb);
|
||||||
|
module.exports = SyncDb;
|
|
@ -0,0 +1,127 @@
|
||||||
|
|
||||||
|
const SyncConnector = require('./sync-connector');
|
||||||
|
require('./sync-db');
|
||||||
|
require('./sync-sip');
|
||||||
|
require('./sync-ldap');
|
||||||
|
require('./sync-samba');
|
||||||
|
|
||||||
|
module.exports = class SyncEngine {
|
||||||
|
async init($) {
|
||||||
|
let accountConfig = await $.AccountConfig.findOne({
|
||||||
|
fields: ['homedir', 'shell', 'idBase']
|
||||||
|
});
|
||||||
|
let mailConfig = await $.MailConfig.findOne({
|
||||||
|
fields: ['domain']
|
||||||
|
});
|
||||||
|
|
||||||
|
let connectors = [];
|
||||||
|
|
||||||
|
for (let ConnectorClass of SyncConnector.connectors) {
|
||||||
|
let connector = new ConnectorClass();
|
||||||
|
Object.assign(connector, {
|
||||||
|
se: this,
|
||||||
|
$
|
||||||
|
});
|
||||||
|
if (!await connector.init()) continue;
|
||||||
|
connectors.push(connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
connectors,
|
||||||
|
$,
|
||||||
|
accountConfig,
|
||||||
|
mailConfig
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deinit() {
|
||||||
|
for (let connector of this.connectors)
|
||||||
|
await connector.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync(userName, password, syncGroups) {
|
||||||
|
let {
|
||||||
|
$,
|
||||||
|
accountConfig,
|
||||||
|
mailConfig
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (!userName) return;
|
||||||
|
userName = userName.toLowerCase();
|
||||||
|
|
||||||
|
// Skip conflicting users
|
||||||
|
if (['administrator', 'root'].indexOf(userName) >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let user = await $.Account.findOne({
|
||||||
|
where: {name: userName},
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'nickname',
|
||||||
|
'email',
|
||||||
|
'lang',
|
||||||
|
'roleFk',
|
||||||
|
'sync',
|
||||||
|
'active',
|
||||||
|
'created',
|
||||||
|
'bcryptPassword',
|
||||||
|
'updated'
|
||||||
|
],
|
||||||
|
include: {
|
||||||
|
relation: 'roles',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'inherits',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let extraParams;
|
||||||
|
let hasAccount = false;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
hasAccount = user.active
|
||||||
|
&& await $.UserAccount.exists(user.id);
|
||||||
|
|
||||||
|
extraParams = {
|
||||||
|
corporateMail: `${userName}@${mailConfig.domain}`,
|
||||||
|
uidNumber: accountConfig.idBase + user.id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = {
|
||||||
|
user,
|
||||||
|
extraParams,
|
||||||
|
hasAccount,
|
||||||
|
accountConfig,
|
||||||
|
mailConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
let errs = [];
|
||||||
|
|
||||||
|
for (let connector of this.connectors) {
|
||||||
|
try {
|
||||||
|
await connector.sync(info, userName, password);
|
||||||
|
if (syncGroups)
|
||||||
|
await connector.syncGroups(info, userName);
|
||||||
|
} catch (err) {
|
||||||
|
errs.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errs.length) throw errs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers() {
|
||||||
|
let usersToSync = new Set();
|
||||||
|
|
||||||
|
for (let connector of this.connectors)
|
||||||
|
await connector.getUsers(usersToSync);
|
||||||
|
|
||||||
|
return usersToSync;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,204 @@
|
||||||
|
|
||||||
|
const SyncConnector = require('./sync-connector');
|
||||||
|
const nthash = require('smbhash').nthash;
|
||||||
|
const ldap = require('./ldapjs-extra');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
class SyncLdap extends SyncConnector {
|
||||||
|
async init() {
|
||||||
|
let ldapConfig = await this.$.LdapConfig.findOne({
|
||||||
|
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
||||||
|
});
|
||||||
|
if (!ldapConfig) return false;
|
||||||
|
|
||||||
|
let client = ldap.createClient({
|
||||||
|
url: `ldap://${ldapConfig.host}:389`
|
||||||
|
});
|
||||||
|
|
||||||
|
let ldapPassword = Buffer
|
||||||
|
.from(ldapConfig.password, 'base64')
|
||||||
|
.toString('ascii');
|
||||||
|
await client.bind(ldapConfig.rdn, ldapPassword);
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
ldapConfig,
|
||||||
|
client
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deinit() {
|
||||||
|
if (this.client)
|
||||||
|
await this.client.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync(info, userName, password) {
|
||||||
|
let {
|
||||||
|
ldapConfig,
|
||||||
|
client,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let {
|
||||||
|
user,
|
||||||
|
hasAccount,
|
||||||
|
extraParams,
|
||||||
|
accountConfig
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
let res = await client.search(ldapConfig.baseDn, {
|
||||||
|
scope: 'sub',
|
||||||
|
attributes: ['userPassword', 'sambaNTPassword'],
|
||||||
|
filter: `&(uid=${userName})`
|
||||||
|
});
|
||||||
|
|
||||||
|
let oldUser;
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
res.on('error', reject);
|
||||||
|
res.on('searchEntry', e => oldUser = e.object);
|
||||||
|
res.on('end', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||||
|
await client.del(dn);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasAccount) {
|
||||||
|
if (oldUser)
|
||||||
|
console.log(` -> '${userName}' removed from LDAP`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nickname = user.nickname || userName;
|
||||||
|
let nameArgs = nickname.trim().split(' ');
|
||||||
|
let sn = nameArgs.length > 1
|
||||||
|
? nameArgs.splice(1).join(' ')
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||||
|
let newEntry = {
|
||||||
|
uid: userName,
|
||||||
|
objectClass: [
|
||||||
|
'inetOrgPerson',
|
||||||
|
'posixAccount',
|
||||||
|
'sambaSamAccount'
|
||||||
|
],
|
||||||
|
cn: nickname,
|
||||||
|
displayName: nickname,
|
||||||
|
givenName: nameArgs[0],
|
||||||
|
sn,
|
||||||
|
mail: extraParams.corporateMail,
|
||||||
|
preferredLanguage: user.lang,
|
||||||
|
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
||||||
|
loginShell: accountConfig.shell,
|
||||||
|
uidNumber: extraParams.uidNumber,
|
||||||
|
gidNumber: accountConfig.idBase + user.roleFk,
|
||||||
|
sambaSID: '-'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
let salt = crypto
|
||||||
|
.randomBytes(8)
|
||||||
|
.toString('base64');
|
||||||
|
|
||||||
|
let hash = crypto.createHash('sha1');
|
||||||
|
hash.update(password);
|
||||||
|
hash.update(salt, 'binary');
|
||||||
|
let digest = hash.digest('binary');
|
||||||
|
|
||||||
|
let ssha = Buffer
|
||||||
|
.from(digest + salt, 'binary')
|
||||||
|
.toString('base64');
|
||||||
|
|
||||||
|
Object.assign(newEntry, {
|
||||||
|
userPassword: `{SSHA}${ssha}`,
|
||||||
|
sambaNTPassword: nthash(password)
|
||||||
|
});
|
||||||
|
} else if (oldUser) {
|
||||||
|
Object.assign(newEntry, {
|
||||||
|
userPassword: oldUser.userPassword,
|
||||||
|
sambaNTPassword: oldUser.sambaNTPassword
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let prop in newEntry) {
|
||||||
|
if (newEntry[prop] == null)
|
||||||
|
delete newEntry[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.add(dn, newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncGroups(info, userName) {
|
||||||
|
let {
|
||||||
|
ldapConfig,
|
||||||
|
client
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let {
|
||||||
|
user,
|
||||||
|
hasAccount
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
let res = await client.search(ldapConfig.groupDn, {
|
||||||
|
scope: 'sub',
|
||||||
|
attributes: ['dn'],
|
||||||
|
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
||||||
|
});
|
||||||
|
|
||||||
|
let oldGroups = [];
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
res.on('error', reject);
|
||||||
|
res.on('searchEntry', e => oldGroups.push(e.object));
|
||||||
|
res.on('end', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
let reqs = [];
|
||||||
|
for (let oldGroup of oldGroups) {
|
||||||
|
let change = new ldap.Change({
|
||||||
|
operation: 'delete',
|
||||||
|
modification: {memberUid: userName}
|
||||||
|
});
|
||||||
|
reqs.push(client.modify(oldGroup.dn, change));
|
||||||
|
}
|
||||||
|
await Promise.all(reqs);
|
||||||
|
|
||||||
|
if (!hasAccount) return;
|
||||||
|
|
||||||
|
reqs = [];
|
||||||
|
for (let role of user.roles()) {
|
||||||
|
let change = new ldap.Change({
|
||||||
|
operation: 'add',
|
||||||
|
modification: {memberUid: userName}
|
||||||
|
});
|
||||||
|
let roleName = role.inherits().name;
|
||||||
|
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
|
||||||
|
reqs.push(client.modify(dn, change));
|
||||||
|
}
|
||||||
|
await Promise.all(reqs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers(usersToSync) {
|
||||||
|
let {
|
||||||
|
ldapConfig,
|
||||||
|
client
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let res = await client.search(ldapConfig.baseDn, {
|
||||||
|
scope: 'sub',
|
||||||
|
attributes: ['uid'],
|
||||||
|
filter: `uid=*`
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
res.on('error', reject);
|
||||||
|
res.on('searchEntry', e => usersToSync.add(e.object.uid));
|
||||||
|
res.on('end', resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncConnector.connectors.push(SyncLdap);
|
||||||
|
module.exports = SyncLdap;
|
|
@ -0,0 +1,93 @@
|
||||||
|
|
||||||
|
const SyncConnector = require('./sync-connector');
|
||||||
|
const ssh = require('node-ssh');
|
||||||
|
|
||||||
|
class SyncSamba extends SyncConnector {
|
||||||
|
async init() {
|
||||||
|
let sambaConfig = await this.$.SambaConfig.findOne({
|
||||||
|
fields: ['host', 'sshUser', 'sshPass']
|
||||||
|
});
|
||||||
|
if (!sambaConfig) return false;
|
||||||
|
|
||||||
|
let sshPassword = Buffer
|
||||||
|
.from(sambaConfig.sshPass, 'base64')
|
||||||
|
.toString('ascii');
|
||||||
|
|
||||||
|
let client = new ssh.NodeSSH();
|
||||||
|
await client.connect({
|
||||||
|
host: sambaConfig.host,
|
||||||
|
username: sambaConfig.sshUser,
|
||||||
|
password: sshPassword
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
sambaConfig,
|
||||||
|
client
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deinit() {
|
||||||
|
if (this.client)
|
||||||
|
await this.client.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync(info, userName, password) {
|
||||||
|
let {
|
||||||
|
client
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
let {
|
||||||
|
hasAccount,
|
||||||
|
extraParams
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
if (hasAccount) {
|
||||||
|
try {
|
||||||
|
await client.exec('samba-tool user create', [
|
||||||
|
userName,
|
||||||
|
'--uid-number', `${extraParams.uidNumber}`,
|
||||||
|
'--mail-address', extraParams.corporateMail,
|
||||||
|
'--random-password'
|
||||||
|
]);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
await client.exec('samba-tool user setexpiry', [
|
||||||
|
userName,
|
||||||
|
'--noexpiry'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
await client.exec('samba-tool user setpassword', [
|
||||||
|
userName,
|
||||||
|
'--newpassword', password
|
||||||
|
]);
|
||||||
|
await client.exec('samba-tool user enable', [
|
||||||
|
userName
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.exec('mkhomedir_helper', [
|
||||||
|
userName,
|
||||||
|
'0027'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await client.exec('samba-tool user disable', [
|
||||||
|
userName
|
||||||
|
]);
|
||||||
|
console.log(` -> '${userName}' disabled on Samba`);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers(usersToSync) {
|
||||||
|
let {client} = this;
|
||||||
|
let res = await client.execCommand('samba-tool user list');
|
||||||
|
let users = res.stdout.split('\n');
|
||||||
|
for (let user of users) usersToSync.add(user.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncConnector.connectors.push(SyncSamba);
|
||||||
|
module.exports = SyncSamba;
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
const SyncConnector = require('./sync-connector');
|
||||||
|
|
||||||
|
class SyncSip extends SyncConnector {
|
||||||
|
async sync(info, userName, password) {
|
||||||
|
if (!info.hasAccount || !password) return;
|
||||||
|
|
||||||
|
await this.$.Account.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
||||||
|
[info.user.id, password]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncConnector.connectors.push(SyncSip);
|
||||||
|
module.exports = SyncSip;
|
|
@ -15,3 +15,6 @@ import './basic-data';
|
||||||
import './mail-forwarding';
|
import './mail-forwarding';
|
||||||
import './aliases';
|
import './aliases';
|
||||||
import './roles';
|
import './roles';
|
||||||
|
import './ldap';
|
||||||
|
import './samba';
|
||||||
|
import './posix';
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="LdapConfigs"
|
||||||
|
data="$ctrl.config"
|
||||||
|
id-value="1"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="watcher.submit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="Host"
|
||||||
|
ng-model="$ctrl.config.host"
|
||||||
|
rule="LdapConfig"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="RDN"
|
||||||
|
ng-model="$ctrl.config.rdn"
|
||||||
|
rule="LdapConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Password"
|
||||||
|
ng-model="$ctrl.config.password"
|
||||||
|
info="Password should be base64 encoded"
|
||||||
|
type="password"
|
||||||
|
rule="LdapConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Base DN"
|
||||||
|
ng-model="$ctrl.config.baseDn"
|
||||||
|
rule="LdapConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Filter"
|
||||||
|
ng-model="$ctrl.config.filter"
|
||||||
|
rule="LdapConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Group DN"
|
||||||
|
ng-model="$ctrl.config.groupDn"
|
||||||
|
rule="LdapConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-button
|
||||||
|
label="Undo changes"
|
||||||
|
ng-if="watcher.dataChanged()"
|
||||||
|
ng-click="watcher.loadOriginalData()">
|
||||||
|
</vn-button>
|
||||||
|
<vn-button
|
||||||
|
label="Synchronize now"
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
ng-click="$ctrl.onSynchronizeAll()">
|
||||||
|
</vn-button>
|
||||||
|
<vn-button
|
||||||
|
label="Synchronize user"
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
ng-click="syncUser.show()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
icon="save"
|
||||||
|
vn-tooltip="Save"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="syncUser"
|
||||||
|
on-accept="$ctrl.onUserSync()"
|
||||||
|
on-close="$ctrl.onPassClose()">
|
||||||
|
<tpl-body>
|
||||||
|
<vn-textfield
|
||||||
|
label="Username"
|
||||||
|
ng-model="$ctrl.syncUser">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Password"
|
||||||
|
ng-model="$ctrl.syncPassword"
|
||||||
|
type="password"
|
||||||
|
info="If password is not specified, just user attributes are synchronized">
|
||||||
|
</vn-textfield>
|
||||||
|
</tpl-body>
|
||||||
|
<tpl-buttons>
|
||||||
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
|
<button response="accept" translate>Synchronize</button>
|
||||||
|
</tpl-buttons>
|
||||||
|
</vn-dialog>
|
|
@ -0,0 +1,30 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
import UserError from 'core/lib/user-error';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
onSynchronizeAll() {
|
||||||
|
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
||||||
|
this.$http.patch(`UserAccounts/syncAll`)
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('LDAP users synchronized')));
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserSync() {
|
||||||
|
if (!this.syncUser)
|
||||||
|
throw new UserError('Please enter the username');
|
||||||
|
|
||||||
|
let params = {password: this.syncPassword};
|
||||||
|
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('User synchronized')));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSyncClose() {
|
||||||
|
this.syncUser = '';
|
||||||
|
this.syncPassword = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAccountLdap', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
Host: Host
|
||||||
|
RDN: RDN
|
||||||
|
Base DN: DN base
|
||||||
|
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
||||||
|
Filter: Filtro
|
||||||
|
Group DN: DN grupos
|
||||||
|
Synchronize now: Sincronizar ahora
|
||||||
|
Synchronize user: Sincronizar usuario
|
||||||
|
If password is not specified, just user attributes are synchronized: >-
|
||||||
|
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
|
Synchronizing in the background: Sincronizando en segundo plano
|
||||||
|
LDAP users synchronized: Usuarios LDAP sincronizados
|
||||||
|
Username: Nombre de usuario
|
||||||
|
Synchronize: Sincronizar
|
||||||
|
Please enter the username: Por favor introduce el nombre de usuario
|
||||||
|
User synchronized: Usuario sincronizado
|
|
@ -0,0 +1,69 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="AccountConfigs"
|
||||||
|
data="$ctrl.config"
|
||||||
|
id-value="1"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="watcher.submit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="Homedir"
|
||||||
|
ng-model="$ctrl.config.homedir"
|
||||||
|
rule="AccountConfig"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Shell"
|
||||||
|
ng-model="$ctrl.config.shell"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-input-number
|
||||||
|
label="Id base"
|
||||||
|
ng-model="$ctrl.config.idBase"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-input-number>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-input-number
|
||||||
|
label="Min"
|
||||||
|
ng-model="$ctrl.config.min"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-input-number>
|
||||||
|
<vn-input-number
|
||||||
|
label="Max"
|
||||||
|
ng-model="$ctrl.config.max"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-input-number>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-input-number
|
||||||
|
label="Warn"
|
||||||
|
ng-model="$ctrl.config.warn"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-input-number>
|
||||||
|
<vn-input-number
|
||||||
|
label="Inact"
|
||||||
|
ng-model="$ctrl.config.inact"
|
||||||
|
rule="AccountConfig">
|
||||||
|
</vn-input-number>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-button
|
||||||
|
label="Undo changes"
|
||||||
|
ng-if="watcher.dataChanged()"
|
||||||
|
ng-click="watcher.loadOriginalData()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
icon="save"
|
||||||
|
vn-tooltip="Save"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {}
|
||||||
|
|
||||||
|
ngModule.component('vnAccountPosix', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
Host: Host
|
||||||
|
RDN: RDN
|
||||||
|
Base DN: DN base
|
||||||
|
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
||||||
|
Filter: Filtro
|
||||||
|
Group DN: DN grupos
|
||||||
|
Synchronize now: Sincronizar ahora
|
||||||
|
Synchronize user: Sincronizar usuario
|
||||||
|
If password is not specified, just user attributes are synchronized: >-
|
||||||
|
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
|
Synchronizing in the background: Sincronizando en segundo plano
|
||||||
|
LDAP users synchronized: Usuarios LDAP sincronizados
|
||||||
|
Username: Nombre de usuario
|
||||||
|
Synchronize: Sincronizar
|
||||||
|
Please enter the username: Por favor introduce el nombre de usuario
|
||||||
|
User synchronized: Usuario sincronizado
|
|
@ -9,6 +9,9 @@
|
||||||
{"state": "account.index", "icon": "face"},
|
{"state": "account.index", "icon": "face"},
|
||||||
{"state": "account.role", "icon": "group"},
|
{"state": "account.role", "icon": "group"},
|
||||||
{"state": "account.alias", "icon": "email"},
|
{"state": "account.alias", "icon": "email"},
|
||||||
|
{"state": "account.posix", "icon": "accessibility"},
|
||||||
|
{"state": "account.ldap", "icon": "account_tree"},
|
||||||
|
{"state": "account.samba", "icon": "desktop_windows"},
|
||||||
{"state": "account.acl", "icon": "check"},
|
{"state": "account.acl", "icon": "check"},
|
||||||
{"state": "account.connections", "icon": "share"}
|
{"state": "account.connections", "icon": "share"}
|
||||||
],
|
],
|
||||||
|
@ -174,6 +177,24 @@
|
||||||
"state": "account.alias.card.users",
|
"state": "account.alias.card.users",
|
||||||
"component": "vn-alias-users",
|
"component": "vn-alias-users",
|
||||||
"description": "Users"
|
"description": "Users"
|
||||||
|
}, {
|
||||||
|
"url": "/posix",
|
||||||
|
"state": "account.posix",
|
||||||
|
"component": "vn-account-posix",
|
||||||
|
"description": "Posix",
|
||||||
|
"acl": ["developer"]
|
||||||
|
}, {
|
||||||
|
"url": "/ldap",
|
||||||
|
"state": "account.ldap",
|
||||||
|
"component": "vn-account-ldap",
|
||||||
|
"description": "LDAP",
|
||||||
|
"acl": ["developer"]
|
||||||
|
}, {
|
||||||
|
"url": "/samba",
|
||||||
|
"state": "account.samba",
|
||||||
|
"component": "vn-account-samba",
|
||||||
|
"description": "Samba",
|
||||||
|
"acl": ["developer"]
|
||||||
}, {
|
}, {
|
||||||
"url": "/acl?q",
|
"url": "/acl?q",
|
||||||
"state": "account.acl",
|
"state": "account.acl",
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="SambaConfigs"
|
||||||
|
data="$ctrl.config"
|
||||||
|
id-value="1"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="watcher.submit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="SSH host"
|
||||||
|
ng-model="$ctrl.config.host"
|
||||||
|
rule="SambaConfig"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="User"
|
||||||
|
ng-model="$ctrl.config.sshUser"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Password"
|
||||||
|
ng-model="$ctrl.config.sshPass"
|
||||||
|
info="Password should be base64 encoded"
|
||||||
|
type="password"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-button
|
||||||
|
label="Undo changes"
|
||||||
|
ng-if="watcher.dataChanged()"
|
||||||
|
ng-click="watcher.loadOriginalData()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
icon="save"
|
||||||
|
vn-tooltip="Save"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {}
|
||||||
|
|
||||||
|
ngModule.component('vnAccountSamba', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
SSH host: Host SSH
|
||||||
|
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
|
@ -7,5 +7,8 @@
|
||||||
},
|
},
|
||||||
"SupplierLog": {
|
"SupplierLog": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"SupplierContact": {
|
||||||
|
"dataSource": "vn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"name": "SupplierContact",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "supplierContact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "Number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"supplierFk": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"observation": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"supplier": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Supplier",
|
||||||
|
"foreignKey": "supplierFk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="model"
|
||||||
|
url="SupplierContacts"
|
||||||
|
link="{supplierFk: $ctrl.$params.id}"
|
||||||
|
data="contacts"
|
||||||
|
auto-load="true">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
data="contacts"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<div ng-repeat="contact in contacts" class="contact">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Name"
|
||||||
|
ng-model="contact.name"
|
||||||
|
rule="SupplierContact">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Phone"
|
||||||
|
ng-model="contact.phone"
|
||||||
|
rule="SupplierContact">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Mobile"
|
||||||
|
ng-model="contact.mobile"
|
||||||
|
rule="SupplierContact">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Email"
|
||||||
|
ng-model="contact.email"
|
||||||
|
rule="SupplierContact">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Notes"
|
||||||
|
ng-model="contact.observation"
|
||||||
|
rule="SupplierContact"
|
||||||
|
title="{{contact.observation}}">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-none>
|
||||||
|
<vn-icon-button
|
||||||
|
vn-tooltip="Remove contact"
|
||||||
|
icon="delete"
|
||||||
|
tabindex="-1"
|
||||||
|
ng-click="model.remove($index)">
|
||||||
|
</vn-icon-button>
|
||||||
|
</vn-none>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-vertical>
|
||||||
|
</div>
|
||||||
|
<vn-one>
|
||||||
|
<vn-icon-button
|
||||||
|
vn-bind="+"
|
||||||
|
vn-tooltip="Add contact"
|
||||||
|
icon="add_circle"
|
||||||
|
ng-click="$ctrl.add()">
|
||||||
|
</vn-icon-button>
|
||||||
|
</vn-one>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-submit label="Save"></vn-submit>
|
||||||
|
</vn-button-bar>
|
||||||
|
</form>
|
|
@ -0,0 +1,27 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import './style.scss';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
class Controller extends Section {
|
||||||
|
add() {
|
||||||
|
this.$.model.insert({
|
||||||
|
supplierFk: this.supplier.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.$.watcher.check();
|
||||||
|
this.$.model.save().then(() => {
|
||||||
|
this.$.watcher.notifySaved();
|
||||||
|
this.$.watcher.updateOriginalData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnSupplierContact', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
supplier: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
|
||||||
|
.contact {
|
||||||
|
max-width: $width-lg;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border: 1px solid $color-marginal;
|
||||||
|
}
|
|
@ -7,3 +7,4 @@ import './index/';
|
||||||
import './search-panel';
|
import './search-panel';
|
||||||
import './log';
|
import './log';
|
||||||
import './summary';
|
import './summary';
|
||||||
|
import './contact';
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
{"state": "supplier.index", "icon": "icon-supplier"}
|
{"state": "supplier.index", "icon": "icon-supplier"}
|
||||||
],
|
],
|
||||||
"card": [
|
"card": [
|
||||||
|
{"state": "supplier.card.contact", "icon": "contact_phone"},
|
||||||
{"state": "supplier.card.log", "icon": "history"}
|
{"state": "supplier.card.log", "icon": "history"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -19,17 +20,20 @@
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"component": "vn-supplier",
|
"component": "vn-supplier",
|
||||||
"description": "Suppliers"
|
"description": "Suppliers"
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"url": "/index?q",
|
"url": "/index?q",
|
||||||
"state": "supplier.index",
|
"state": "supplier.index",
|
||||||
"component": "vn-supplier-index",
|
"component": "vn-supplier-index",
|
||||||
"description": "Suppliers"
|
"description": "Suppliers"
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"url": "/:id",
|
"url": "/:id",
|
||||||
"state": "supplier.card",
|
"state": "supplier.card",
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"component": "vn-supplier-card"
|
"component": "vn-supplier-card"
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"url": "/summary",
|
"url": "/summary",
|
||||||
"state": "supplier.card.summary",
|
"state": "supplier.card.summary",
|
||||||
"component": "vn-supplier-summary",
|
"component": "vn-supplier-summary",
|
||||||
|
@ -37,11 +41,21 @@
|
||||||
"params": {
|
"params": {
|
||||||
"supplier": "$ctrl.supplier"
|
"supplier": "$ctrl.supplier"
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"url" : "/log",
|
"url" : "/log",
|
||||||
"state": "supplier.card.log",
|
"state": "supplier.card.log",
|
||||||
"component": "vn-supplier-log",
|
"component": "vn-supplier-log",
|
||||||
"description": "Log"
|
"description": "Log"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/contact",
|
||||||
|
"state": "supplier.card.contact",
|
||||||
|
"component": "vn-supplier-contact",
|
||||||
|
"description": "Contacts",
|
||||||
|
"params": {
|
||||||
|
"supplier": "$ctrl.supplier"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,7 @@
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
"i18n": "^0.8.4",
|
"i18n": "^0.8.4",
|
||||||
"imap": "^0.8.19",
|
"imap": "^0.8.19",
|
||||||
"ldapjs": "^1.0.2",
|
"ldapjs": "^2.2.0",
|
||||||
"loopback": "^3.26.0",
|
"loopback": "^3.26.0",
|
||||||
"loopback-boot": "^2.27.1",
|
"loopback-boot": "^2.27.1",
|
||||||
"loopback-component-explorer": "^6.5.0",
|
"loopback-component-explorer": "^6.5.0",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue