mirror of
https://github.com/moparisthebest/kaiwa
synced 2025-02-16 07:00:09 -05:00
LDAP users management
This commit is contained in:
parent
fc894a4c35
commit
1b15f112d8
@ -9,6 +9,7 @@ var StanzaIO = require('stanza.io');
|
||||
|
||||
var AppState = require('./models/state');
|
||||
var MeModel = require('./models/me');
|
||||
var LdapUsers = require('./models/ldapUsers');
|
||||
var MainView = require('./views/main');
|
||||
var Router = require('./router');
|
||||
var Storage = require('./storage');
|
||||
@ -32,8 +33,8 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
config = JSON.parse(config);
|
||||
config.useStreamManagement = true;
|
||||
app.config = JSON.parse(config);
|
||||
app.config.useStreamManagement = true;
|
||||
|
||||
_.extend(this, Backbone.Events);
|
||||
|
||||
@ -51,11 +52,11 @@ module.exports = {
|
||||
app.mucInfos = [];
|
||||
},
|
||||
function (cb) {
|
||||
app.storage.profiles.get(config.jid, function (err, res) {
|
||||
app.storage.profiles.get(app.config.jid, function (err, res) {
|
||||
if (res) {
|
||||
profile = res;
|
||||
profile.jid = {full: config.jid, bare: config.jid};
|
||||
config.rosterVer = res.rosterVer;
|
||||
profile.jid = {full: app.config.jid, bare: app.config.jid};
|
||||
app.config.rosterVer = res.rosterVer;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
@ -70,7 +71,7 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
self.api = window.client = StanzaIO.createClient(config);
|
||||
self.api = window.client = StanzaIO.createClient(app.config);
|
||||
client.use(pushNotifications);
|
||||
xmppEventHandlers(self.api, self);
|
||||
|
||||
@ -121,6 +122,8 @@ module.exports = {
|
||||
});
|
||||
self.view.render();
|
||||
|
||||
app.ldapUsers = new LdapUsers();
|
||||
|
||||
if (me.contacts.length) {
|
||||
start();
|
||||
} else {
|
||||
|
4
clientapp/libraries/jquery-impromptu.js
vendored
Normal file
4
clientapp/libraries/jquery-impromptu.js
vendored
Normal file
File diff suppressed because one or more lines are too long
79
clientapp/models/ldapUser.js
Normal file
79
clientapp/models/ldapUser.js
Normal file
@ -0,0 +1,79 @@
|
||||
/*global app, me, client, URL*/
|
||||
"use strict";
|
||||
|
||||
var _ = require('underscore');
|
||||
var HumanModel = require('human-model');
|
||||
|
||||
exports.ldapData = function (data) {
|
||||
if (!data) data = {};
|
||||
data.uid = app.config.jid.substring(0, app.config.jid.indexOf('@'));
|
||||
data.password = app.config.credentials.password;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.user = HumanModel.define({
|
||||
initialize: function (attrs) {
|
||||
|
||||
},
|
||||
type: 'ldapUser',
|
||||
props: {
|
||||
id: ['string', true, false],
|
||||
cn: ['string', true, false],
|
||||
sn: ['string', false, ''],
|
||||
givenName: ['string', false, ''],
|
||||
displayName: ['string', false, ''],
|
||||
mail: ['string', false, ''],
|
||||
objectClass: ['array', false, []]
|
||||
},
|
||||
save: function(userInfos, cb) {
|
||||
var change = false;
|
||||
var oldValues = {};
|
||||
for (var property in userInfos) {
|
||||
if (userInfos.hasOwnProperty(property) && this[property] !== undefined) {
|
||||
oldValues[property] = this[property];
|
||||
if (this[property] != userInfos[property])
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!change)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
$.post('/ldap/user/' + this.id, exports.ldapData(userInfos), function(result) {
|
||||
result = JSON.parse(result);
|
||||
|
||||
if (result) {
|
||||
for (var property in userInfos) {
|
||||
if (userInfos.hasOwnProperty(property) && self[property] !== undefined) {
|
||||
self[property] = userInfos[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var property in oldValues) {
|
||||
if (oldValues.hasOwnProperty(property) && self[property] !== undefined) {
|
||||
self[property] = '';
|
||||
self[property] = oldValues[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
},
|
||||
changePassword: function(newPassword) {
|
||||
var self = this;
|
||||
|
||||
$.post('/ldap/user/' + this.id + '/password', exports.ldapData({newPassword: newPassword}), function(result) {
|
||||
result = JSON.parse(result);
|
||||
if (!result) {
|
||||
app.ldapUsers.fetch();
|
||||
}
|
||||
});
|
||||
},
|
||||
meIsAdmin: function() {
|
||||
return me.isAdmin;
|
||||
}
|
||||
});
|
71
clientapp/models/ldapUsers.js
Normal file
71
clientapp/models/ldapUsers.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*global app, client*/
|
||||
"use strict";
|
||||
|
||||
var BaseCollection = require('./baseCollection');
|
||||
var ldapUser = require('./ldapUser');
|
||||
|
||||
module.exports = BaseCollection.extend({
|
||||
type: 'ldapUsers',
|
||||
model: ldapUser.user,
|
||||
comparator: function (model1, model2) {
|
||||
var name1 = model1.displayName.toLowerCase();
|
||||
var name2 = model2.displayName.toLowerCase();
|
||||
if (name1 === name2) {
|
||||
return 0;
|
||||
}
|
||||
if (name1 < name2) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
initialize: function (model, options) {
|
||||
this.bind('change', this.sort, this);
|
||||
},
|
||||
fetch: function (cb) {
|
||||
var self = this;
|
||||
|
||||
$.post('/ldap/users', ldapUser.ldapData(), function(users) {
|
||||
var toRemove = [];
|
||||
for ( var i = 0; i < self.models.length; i++) {
|
||||
toRemove.push(self.models[i].id);
|
||||
}
|
||||
|
||||
users = JSON.parse(users);
|
||||
users.forEach(function(user) {
|
||||
var existing = self.get(user.id);
|
||||
if (!existing) {
|
||||
self.add(user);
|
||||
}
|
||||
|
||||
var index = toRemove.indexOf(user.id);
|
||||
if (index > -1) {
|
||||
toRemove.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
self.remove(toRemove);
|
||||
|
||||
if (cb) cb();
|
||||
});
|
||||
},
|
||||
addUser: function (id) {
|
||||
var self = this;
|
||||
|
||||
$.post('/ldap/users/add', ldapUser.ldapData({newUid: id}), function(result) {
|
||||
result = JSON.parse(result);
|
||||
if (result) {
|
||||
self.fetch();
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteUser: function (id) {
|
||||
var self = this;
|
||||
|
||||
$.post('/ldap/users/delete', ldapUser.ldapData({removeUid: id}), function(result) {
|
||||
result = JSON.parse(result);
|
||||
if (result) {
|
||||
self.fetch();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -76,6 +76,12 @@ module.exports = HumanModel.define({
|
||||
return this.soundEnabled ? "primary" : "secondary";
|
||||
}
|
||||
},
|
||||
isAdmin: {
|
||||
deps: ['jid'],
|
||||
fn: function () {
|
||||
return this.jid.local === SERVER_CONFIG.admin ? 'meIsAdmin' : '';
|
||||
}
|
||||
}
|
||||
},
|
||||
setActiveContact: function (jid) {
|
||||
var prev = this.getContact(this._activeContact);
|
||||
@ -105,6 +111,9 @@ module.exports = HumanModel.define({
|
||||
self.avatar = avatar.uri;
|
||||
});
|
||||
},
|
||||
hasLdapUsers: function () {
|
||||
return app.ldapUsers.length > 0 ? 'hasLdapUsers' : '';
|
||||
},
|
||||
setSoundNotification: function(enable) {
|
||||
this.soundEnabled = enable;
|
||||
},
|
||||
|
@ -4,13 +4,15 @@
|
||||
var crypto = require('crypto');
|
||||
var BasePage = require('./base');
|
||||
var templates = require('../templates');
|
||||
|
||||
var LDAPUserItem = require('../views/ldapUserItem');
|
||||
|
||||
module.exports = BasePage.extend({
|
||||
template: templates.pages.settings,
|
||||
classBindings: {
|
||||
shouldAskForAlertsPermission: '.enableAlerts',
|
||||
soundEnabledClass: '.soundNotifs'
|
||||
soundEnabledClass: '.soundNotifs',
|
||||
hasLdapUsers: '#ldapSettings',
|
||||
isAdmin: '#newLdapUser'
|
||||
},
|
||||
srcBindings: {
|
||||
avatar: '#avatarChanger img'
|
||||
@ -24,13 +26,19 @@ module.exports = BasePage.extend({
|
||||
'click .soundNotifs': 'handleSoundNotifs',
|
||||
'dragover': 'handleAvatarChangeDragOver',
|
||||
'drop': 'handleAvatarChange',
|
||||
'change #uploader': 'handleAvatarChange'
|
||||
'change #uploader': 'handleAvatarChange',
|
||||
'keydown #newLdapUser': 'addLdapUser',
|
||||
},
|
||||
initialize: function (spec) {
|
||||
this.render();
|
||||
this.listenTo(this, 'deleteLdapUser', this.deleteLdapUser);
|
||||
var self = this;
|
||||
app.ldapUsers.fetch(function () {
|
||||
self.render();
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
this.renderAndBind();
|
||||
this.renderCollection(app.ldapUsers, LDAPUserItem, this.$('#ldapUsers'));
|
||||
return this;
|
||||
},
|
||||
enableAlerts: function () {
|
||||
@ -93,4 +101,14 @@ module.exports = BasePage.extend({
|
||||
handleSoundNotifs: function (e) {
|
||||
this.model.setSoundNotification(!this.model.soundEnabled);
|
||||
},
|
||||
addLdapUser: function (e) {
|
||||
if (e.which === 13 && !e.shiftKey) {
|
||||
var id = e.target.value;
|
||||
e.target.value = '';
|
||||
app.ldapUsers.addUser(id);
|
||||
}
|
||||
},
|
||||
deleteLdapUser: function (id) {
|
||||
app.ldapUsers.deleteUser(id);
|
||||
}
|
||||
});
|
||||
|
@ -207,6 +207,15 @@ exports.includes.embeds = function anonymous(locals) {
|
||||
return buf.join("");
|
||||
};
|
||||
|
||||
// ldapUserItem.jade compiled template
|
||||
exports.includes.ldapUserItem = function anonymous(locals) {
|
||||
var buf = [];
|
||||
with (locals || {}) {
|
||||
buf.push('<li class="ldapUser"><span class="name"></span><span class="delete fa fa-trash"></span><span class="fa fa-plus"></span><div class="wrap"><span class="inputLabel">Display Name</span><input type="text" class="displayName"/><span class="inputLabel">First Name</span><input type="text" class="givenName"/><span class="inputLabel">Last Name</span><input type="text" class="sn"/><span class="inputLabel">Email</span><input type="text" class="mail"/><button class="primary small changePassword">Change Password</button></div></li>');
|
||||
}
|
||||
return buf.join("");
|
||||
};
|
||||
|
||||
// message.jade compiled template
|
||||
exports.includes.message = function anonymous(locals) {
|
||||
var buf = [];
|
||||
@ -512,7 +521,7 @@ exports.pages.groupchat = function anonymous(locals) {
|
||||
exports.pages.settings = function anonymous(locals) {
|
||||
var buf = [];
|
||||
with (locals || {}) {
|
||||
buf.push('<section class="page main"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 30 30" height="30" width="30"><g transform="scale(0.5)"><path d="M37.418,34.3c-2.1-2.721-2.622-6.352-1.292-9.604c0.452-1.107,1.104-2.1,1.902-2.951 c-0.753-0.877-1.573-1.697-2.507-2.387l-2.609,1.408c-1.05-0.629-2.194-1.112-3.414-1.421l-0.845-2.833 c-0.75-0.112-1.512-0.188-2.287-0.188c-0.783,0-1.54,0.075-2.288,0.188l-0.851,2.833c-1.215,0.309-2.355,0.792-3.41,1.421 l-2.614-1.408c-1.229,0.912-2.318,2-3.228,3.231l1.404,2.612c-0.628,1.053-1.11,2.193-1.419,3.411l-2.832,0.849 c-0.114,0.75-0.187,1.508-0.187,2.287c0,0.778,0.073,1.537,0.187,2.286l2.832,0.848c0.309,1.22,0.791,2.36,1.419,3.413l-1.404,2.61 c0.909,1.231,1.999,2.321,3.228,3.231l2.614-1.406c1.055,0.628,2.195,1.11,3.41,1.42l0.851,2.832 c0.748,0.114,1.505,0.188,2.288,0.188c0.775,0,1.537-0.074,2.287-0.188l0.845-2.832c1.224-0.31,2.364-0.792,3.414-1.42l0.062,0.033 l2.045-3.02L37.418,34.3z M26.367,36.776c-2.777,0-5.027-2.253-5.027-5.027c0-2.775,2.25-5.028,5.027-5.028 c2.774,0,5.024,2.253,5.024,5.028C31.391,34.523,29.141,36.776,26.367,36.776z"></path><path d="M51.762,24.505l-1.125-0.459l-1.451,3.55c-0.814,1.993-2.832,3.054-4.505,2.37l-0.355-0.144 c-1.673-0.686-2.37-2.856-1.558-4.849l1.451-3.551l-1.125-0.46c-2.225,0.608-4.153,2.2-5.092,4.501 c-1.225,2.997-0.422,6.312,1.771,8.436l-2.958,6.812l-2.204,3.249l-0.007,2.281l5.275,2.154l1.593-1.633l0.7-3.861l2.901-6.836 c3.049,0.018,5.947-1.785,7.174-4.779C53.186,28.983,52.924,26.499,51.762,24.505z"></path></g></svg><h1 id="title">Settings</h1><div id="avatarChanger"><h4>Change Avatar</h4><div class="uploadRegion"><p>Drag and drop a new avatar here</p><img/><form><input id="uploader" type="file"/></form></div></div><div><h4>Desktop Integration</h4><button class="enableAlerts">Enable alerts</button><button class="primary installFirefox">Install app</button><button class="soundNotifs">sound notifications</button></div><div><button class="logout">Logout</button></div></section>');
|
||||
buf.push('<section class="page main"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 30 30" height="30" width="30"><g transform="scale(0.5)"><path d="M37.418,34.3c-2.1-2.721-2.622-6.352-1.292-9.604c0.452-1.107,1.104-2.1,1.902-2.951 c-0.753-0.877-1.573-1.697-2.507-2.387l-2.609,1.408c-1.05-0.629-2.194-1.112-3.414-1.421l-0.845-2.833 c-0.75-0.112-1.512-0.188-2.287-0.188c-0.783,0-1.54,0.075-2.288,0.188l-0.851,2.833c-1.215,0.309-2.355,0.792-3.41,1.421 l-2.614-1.408c-1.229,0.912-2.318,2-3.228,3.231l1.404,2.612c-0.628,1.053-1.11,2.193-1.419,3.411l-2.832,0.849 c-0.114,0.75-0.187,1.508-0.187,2.287c0,0.778,0.073,1.537,0.187,2.286l2.832,0.848c0.309,1.22,0.791,2.36,1.419,3.413l-1.404,2.61 c0.909,1.231,1.999,2.321,3.228,3.231l2.614-1.406c1.055,0.628,2.195,1.11,3.41,1.42l0.851,2.832 c0.748,0.114,1.505,0.188,2.288,0.188c0.775,0,1.537-0.074,2.287-0.188l0.845-2.832c1.224-0.31,2.364-0.792,3.414-1.42l0.062,0.033 l2.045-3.02L37.418,34.3z M26.367,36.776c-2.777,0-5.027-2.253-5.027-5.027c0-2.775,2.25-5.028,5.027-5.028 c2.774,0,5.024,2.253,5.024,5.028C31.391,34.523,29.141,36.776,26.367,36.776z"></path><path d="M51.762,24.505l-1.125-0.459l-1.451,3.55c-0.814,1.993-2.832,3.054-4.505,2.37l-0.355-0.144 c-1.673-0.686-2.37-2.856-1.558-4.849l1.451-3.551l-1.125-0.46c-2.225,0.608-4.153,2.2-5.092,4.501 c-1.225,2.997-0.422,6.312,1.771,8.436l-2.958,6.812l-2.204,3.249l-0.007,2.281l5.275,2.154l1.593-1.633l0.7-3.861l2.901-6.836 c3.049,0.018,5.947-1.785,7.174-4.779C53.186,28.983,52.924,26.499,51.762,24.505z"></path></g></svg><h1 id="title">Settings</h1><div id="avatarChanger"><h4>Change Avatar</h4><div class="uploadRegion"><p>Drag and drop a new avatar here</p><img/><form><input id="uploader" type="file"/></form></div></div><div><h4>Desktop Integration</h4><button class="enableAlerts">Enable alerts</button><button class="primary installFirefox">Install app</button><button class="soundNotifs">sound notifications</button></div><div id="ldapSettings"><h4>LDAP settings</h4><ul id="ldapUsers"></ul><input type="text" placeholder="add a ldap user" id="newLdapUser"/></div><div><button class="logout">Logout</button></div></section>');
|
||||
}
|
||||
return buf.join("");
|
||||
};
|
||||
|
14
clientapp/templates/includes/ldapUserItem.jade
Normal file
14
clientapp/templates/includes/ldapUserItem.jade
Normal file
@ -0,0 +1,14 @@
|
||||
li.ldapUser
|
||||
span.name
|
||||
span.delete.fa.fa-trash
|
||||
span.fa.fa-plus
|
||||
.wrap
|
||||
span.inputLabel Display Name
|
||||
input(type="text").displayName
|
||||
span.inputLabel First Name
|
||||
input(type="text").givenName
|
||||
span.inputLabel Last Name
|
||||
input(type="text").sn
|
||||
span.inputLabel Email
|
||||
input(type="text").mail
|
||||
button.primary.small.changePassword Change Password
|
@ -20,5 +20,10 @@ section.page.main
|
||||
button.primary.installFirefox Install app
|
||||
button.soundNotifs sound notifications
|
||||
|
||||
div#ldapSettings
|
||||
h4 LDAP settings
|
||||
ul#ldapUsers
|
||||
input(type="text", placeholder="add a ldap user")#newLdapUser
|
||||
|
||||
div
|
||||
button.logout Logout
|
||||
|
140
clientapp/views/ldapUserItem.js
Normal file
140
clientapp/views/ldapUserItem.js
Normal file
@ -0,0 +1,140 @@
|
||||
/*global $, app, me*/
|
||||
"use strict";
|
||||
|
||||
var _ = require('underscore');
|
||||
var HumanView = require('human-view');
|
||||
var templates = require('../templates');
|
||||
|
||||
|
||||
module.exports = HumanView.extend({
|
||||
template: templates.includes.ldapUserItem,
|
||||
classBindings: {
|
||||
meIsAdmin: '.delete, .fa-plus, .fa-minus, .wrap'
|
||||
},
|
||||
textBindings: {
|
||||
id: '.name'
|
||||
},
|
||||
inputBindings: {
|
||||
displayName: '.displayName',
|
||||
givenName: '.givenName',
|
||||
sn: '.sn',
|
||||
mail: '.mail'
|
||||
},
|
||||
events: {
|
||||
'click .fa-plus': 'handleDisplayUser',
|
||||
'click .fa-minus': 'handleDisplayUser',
|
||||
'click .delete': 'deleteUser',
|
||||
'blur input': 'saveInfos',
|
||||
'focus input': 'handleInputFocus',
|
||||
'select input': 'handleInputSelect',
|
||||
'click .changePassword': 'changePassword'
|
||||
},
|
||||
render: function () {
|
||||
this.renderAndBind();
|
||||
return this;
|
||||
},
|
||||
handleDisplayUser: function (e) {
|
||||
var icon = $(e.target);
|
||||
var wrap = $(e.delegateTarget).find('.wrap');
|
||||
|
||||
if (icon.hasClass('fa-plus')) {
|
||||
wrap.show();
|
||||
icon.removeClass('fa-plus');
|
||||
icon.addClass('fa-minus');
|
||||
} else {
|
||||
wrap.hide();
|
||||
icon.removeClass('fa-minus');
|
||||
icon.addClass('fa-plus');
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
handleInputFocus: function (e) {
|
||||
this.inputWithFocus = e.target;
|
||||
},
|
||||
handleInputSelect: function (e) {
|
||||
this.inputWithSelect = e.target;
|
||||
},
|
||||
saveInfos: function (e) {
|
||||
var input = e.target;
|
||||
var classes = $(input).attr('class');
|
||||
|
||||
var userInfos = {};
|
||||
userInfos[classes] = input.value;
|
||||
|
||||
var ldapUser = this.model;
|
||||
if (userInfos[classes] == ldapUser[classes])
|
||||
return;
|
||||
|
||||
this.inputWithFocus = null;
|
||||
this.inputWithSelect = null;
|
||||
|
||||
var self = this;
|
||||
ldapUser.save(userInfos, function () {
|
||||
if (self.inputWithSelect) {
|
||||
$(self.inputWithSelect).select();
|
||||
self.inputWithSelect = null;
|
||||
self.inputWithFocus = null;
|
||||
}
|
||||
else if (self.inputWithFocus) {
|
||||
$(self.inputWithFocus).focus();
|
||||
self.inputWithFocus = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteUser: function (e) {
|
||||
var self = this;
|
||||
var ldapUser = this.model;
|
||||
$.prompt('Are you sure you want to remove ' + ldapUser.id + '?', {
|
||||
title: 'Remove User',
|
||||
buttons: { "Yes": true, "Cancel": false },
|
||||
persistent: true,
|
||||
submit:function (e, v, m, f) {
|
||||
if (v) {
|
||||
self.parent.trigger('deleteLdapUser', ldapUser.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
changePassword: function (e) {
|
||||
var ldapUser = this.model;
|
||||
$.prompt({
|
||||
state0: {
|
||||
title: 'Change password',
|
||||
html:'<label>New password for ' + this.model.id + ': <input type="password" name="newPassword1" value=""></label><br />',
|
||||
buttons: { "Ok": true, "Cancel": false },
|
||||
focus: "input[name='newPassword1']",
|
||||
persistent: true,
|
||||
submit:function (e, v, m, f) {
|
||||
if (v) {
|
||||
if (f.newPassword1 != '') {
|
||||
e.preventDefault();
|
||||
$.prompt.goToState('state1');
|
||||
return false;
|
||||
} else {
|
||||
$.prompt('Password can not be an empty string.', { title: 'Change password' });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
state1: {
|
||||
title: 'Change password',
|
||||
html:'<label>Confirm password for ' + this.model.id + ': <input type="password" name="newPassword2" value=""></label><br />',
|
||||
buttons: { "Ok": true, "Cancel": false },
|
||||
focus: "input[name='newPassword2']",
|
||||
persistent: true,
|
||||
submit:function (e, v, m, f) {
|
||||
if (v) {
|
||||
if (f.newPassword1 == f.newPassword2) {
|
||||
ldapUser.changePassword(f.newPassword2);
|
||||
} else {
|
||||
$.prompt('the password confirmation must match your password.', { title: 'Change password' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -16,5 +16,13 @@
|
||||
"muc": "chat.localhost",
|
||||
"startup": "",
|
||||
"admin": ""
|
||||
},
|
||||
"ldap": {
|
||||
"address": "127.0.0.1",
|
||||
"user": "cn=admin,dc=example.com",
|
||||
"password": "password",
|
||||
"base": "ou=users,dc=example.com",
|
||||
"filter": "objectClass=person",
|
||||
"group": "cn=mygroup,ou=groups,dc=example.com"
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
"attachmediastream": "1.0.1",
|
||||
"backbone": "1.0.0",
|
||||
"bluebird": "^2.3.2",
|
||||
"body-parser": "1.12.0",
|
||||
"bows": "0.3.0",
|
||||
"browserify": "4.x",
|
||||
"compression": "1.2.2",
|
||||
@ -23,6 +24,7 @@
|
||||
"human-view": "1.8.0",
|
||||
"jade": "1.8.2",
|
||||
"jxt": "^2.7.0",
|
||||
"LDAP": "1.2.1",
|
||||
"moonboots-express": "2.x",
|
||||
"node-uuid": "^1.4.1",
|
||||
"notify.js": "0.0.3",
|
||||
|
124
public/css/jquery-impromptu.css
Normal file
124
public/css/jquery-impromptu.css
Normal file
@ -0,0 +1,124 @@
|
||||
/*! jQuery-Impromptu - v6.0.0 - 2014-12-27
|
||||
* http://trentrichardson.com/Impromptu
|
||||
* Copyright (c) 2014 Trent Richardson; Licensed MIT */
|
||||
.jqifade{
|
||||
position: absolute;
|
||||
background-color: #777777;
|
||||
}
|
||||
div.jqi{
|
||||
width: 400px;
|
||||
max-width:90%;
|
||||
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
font-size: 11px;
|
||||
text-align: left;
|
||||
border: solid 1px #eeeeee;
|
||||
border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
padding: 7px;
|
||||
}
|
||||
div.jqi .jqicontainer{
|
||||
}
|
||||
div.jqi .jqiclose{
|
||||
position: absolute;
|
||||
top: 4px; right: -2px;
|
||||
width: 18px;
|
||||
cursor: default;
|
||||
color: #bbbbbb;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.jqi .jqistate{
|
||||
background-color: #fff;
|
||||
}
|
||||
div.jqi .jqititle{
|
||||
padding: 5px 10px;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
border-bottom: solid 1px #eeeeee;
|
||||
}
|
||||
div.jqi .jqimessage{
|
||||
padding: 10px;
|
||||
line-height: 20px;
|
||||
color: #444444;
|
||||
overflow: auto;
|
||||
}
|
||||
div.jqi .jqibuttons{
|
||||
text-align: right;
|
||||
margin: 0 -7px -7px -7px;
|
||||
border-top: solid 1px #e4e4e4;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 0 0 6px 6px;
|
||||
-moz-border-radius: 0 0 6px 6px;
|
||||
-webkit-border-radius: 0 0 6px 6px;
|
||||
}
|
||||
div.jqi .jqibuttons button{
|
||||
margin: 0;
|
||||
padding: 15px 20px;
|
||||
background-color: transparent;
|
||||
font-weight: normal;
|
||||
border: none;
|
||||
border-left: solid 1px #e4e4e4;
|
||||
color: #777;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
div.jqi .jqibuttons button.jqidefaultbutton{
|
||||
color: #489afe;
|
||||
}
|
||||
div.jqi .jqibuttons button:hover,
|
||||
div.jqi .jqibuttons button:focus{
|
||||
color: #287ade;
|
||||
outline: none;
|
||||
}
|
||||
.jqiwarning .jqi .jqibuttons{
|
||||
background-color: #b95656;
|
||||
}
|
||||
|
||||
/* sub states */
|
||||
div.jqi .jqiparentstate::after{
|
||||
background-color: #777;
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
content: '';
|
||||
position: absolute;
|
||||
top:0;left:0;bottom:0;right:0;
|
||||
border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
-webkit-border-radius: 6px;
|
||||
}
|
||||
div.jqi .jqisubstate{
|
||||
position: absolute;
|
||||
top:0;
|
||||
left: 20%;
|
||||
width: 60%;
|
||||
padding: 7px;
|
||||
border: solid 1px #eeeeee;
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
-moz-border-radius: 0 0 6px 6px;
|
||||
-webkit-border-radius: 0 0 6px 6px;
|
||||
}
|
||||
div.jqi .jqisubstate .jqibuttons button{
|
||||
padding: 10px 18px;
|
||||
}
|
||||
|
||||
/* arrows for tooltips/tours */
|
||||
.jqi .jqiarrow{ position: absolute; height: 0; width:0; line-height: 0; font-size: 0; border: solid 10px transparent;}
|
||||
|
||||
.jqi .jqiarrowtl{ left: 10px; top: -20px; border-bottom-color: #ffffff; }
|
||||
.jqi .jqiarrowtc{ left: 50%; top: -20px; border-bottom-color: #ffffff; margin-left: -10px; }
|
||||
.jqi .jqiarrowtr{ right: 10px; top: -20px; border-bottom-color: #ffffff; }
|
||||
|
||||
.jqi .jqiarrowbl{ left: 10px; bottom: -20px; border-top-color: #ffffff; }
|
||||
.jqi .jqiarrowbc{ left: 50%; bottom: -20px; border-top-color: #ffffff; margin-left: -10px; }
|
||||
.jqi .jqiarrowbr{ right: 10px; bottom: -20px; border-top-color: #ffffff; }
|
||||
|
||||
.jqi .jqiarrowlt{ left: -20px; top: 10px; border-right-color: #ffffff; }
|
||||
.jqi .jqiarrowlm{ left: -20px; top: 50%; border-right-color: #ffffff; margin-top: -10px; }
|
||||
.jqi .jqiarrowlb{ left: -20px; bottom: 10px; border-right-color: #ffffff; }
|
||||
|
||||
.jqi .jqiarrowrt{ right: -20px; top: 10px; border-left-color: #ffffff; }
|
||||
.jqi .jqiarrowrm{ right: -20px; top: 50%; border-left-color: #ffffff; margin-top: -10px; }
|
||||
.jqi .jqiarrowrb{ right: -20px; bottom: 10px; border-left-color: #ffffff; }
|
@ -1594,6 +1594,12 @@ button.secondary:hover:not(:disabled) {
|
||||
right: 200px;
|
||||
z-index: 101;
|
||||
}
|
||||
.main #ldapSettings {
|
||||
display: none;
|
||||
}
|
||||
.main #ldapSettings.hasLdapUsers {
|
||||
display: block;
|
||||
}
|
||||
.main > div {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
@ -1620,6 +1626,66 @@ button.secondary:hover:not(:disabled) {
|
||||
.main > div .soundNotifs.secondary:before {
|
||||
content: 'Enable ';
|
||||
}
|
||||
.main > div #newLdapUser {
|
||||
display: none;
|
||||
width: 500px;
|
||||
height: 20px;
|
||||
padding: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.main > div #newLdapUser.meIsAdmin {
|
||||
display: inline-block;
|
||||
}
|
||||
.main > div #ldapUsers {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: #f9f9f9;
|
||||
list-style-type: none;
|
||||
padding: 5px 5px 5px 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.main > div #ldapUsers .fa-plus,
|
||||
.main > div #ldapUsers .fa-minus,
|
||||
.main > div #ldapUsers .delete {
|
||||
display: none;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
color: #000;
|
||||
}
|
||||
.main > div #ldapUsers .fa-plus:hover,
|
||||
.main > div #ldapUsers .fa-minus:hover,
|
||||
.main > div #ldapUsers .delete:hover {
|
||||
color: #94b021;
|
||||
}
|
||||
.main > div #ldapUsers .fa-plus.meIsAdmin,
|
||||
.main > div #ldapUsers .fa-minus.meIsAdmin,
|
||||
.main > div #ldapUsers .delete.meIsAdmin {
|
||||
display: inline-block;
|
||||
}
|
||||
.main > div #ldapUsers .delete:hover {
|
||||
color: #f00;
|
||||
}
|
||||
.main > div #ldapUsers .wrap {
|
||||
background: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
width: 502px;
|
||||
}
|
||||
.main > div #ldapUsers .wrap.meIsAdmin {
|
||||
display: none;
|
||||
}
|
||||
.main > div #ldapUsers .wrap input {
|
||||
width: 500px;
|
||||
height: 20px;
|
||||
padding: 3px;
|
||||
}
|
||||
.main > div #ldapUsers .wrap .changePassword {
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.uploadRegion {
|
||||
padding: 15px;
|
||||
-moz-border-radius: 3px;
|
||||
|
@ -23,6 +23,11 @@
|
||||
right: 200px
|
||||
z-index: 101
|
||||
|
||||
#ldapSettings
|
||||
display: none
|
||||
&.hasLdapUsers
|
||||
display: block
|
||||
|
||||
> div
|
||||
padding: 20px
|
||||
border-bottom: 1px solid $gray-lighter
|
||||
@ -49,6 +54,57 @@
|
||||
&:before
|
||||
content: 'Enable '
|
||||
|
||||
#newLdapUser
|
||||
display: none
|
||||
width: 500px
|
||||
height: 20px
|
||||
padding: 3px
|
||||
font-size: 12px
|
||||
&.meIsAdmin
|
||||
display: inline-block
|
||||
|
||||
#ldapUsers
|
||||
border-radius: 3px;
|
||||
border: 1px solid $gray-lighter
|
||||
background: lighten($gray-lighter, 80%)
|
||||
list-style-type: none
|
||||
padding: 5px 5px 5px 8px
|
||||
line-height: 20px
|
||||
|
||||
.fa-plus, .fa-minus, .delete
|
||||
display: none
|
||||
margin-left: 5px
|
||||
cursor: pointer
|
||||
color: black
|
||||
&:hover
|
||||
color: $settingsHoverText
|
||||
&.meIsAdmin
|
||||
display: inline-block
|
||||
|
||||
.delete
|
||||
&:hover
|
||||
color: red
|
||||
|
||||
.wrap
|
||||
background: lighten($gray-lighter, 50%)
|
||||
border-radius: 3px;
|
||||
border: 1px solid $gray-lighter
|
||||
padding: 5px
|
||||
font-size: 12px
|
||||
margin-bottom: 5px
|
||||
width: 502px
|
||||
&.meIsAdmin
|
||||
display: none
|
||||
|
||||
input
|
||||
width: 500px
|
||||
height: 20px
|
||||
padding: 3px
|
||||
|
||||
.changePassword
|
||||
margin-top: 4px
|
||||
margin-right: 4px
|
||||
|
||||
.uploadRegion
|
||||
padding: 15px
|
||||
roundall(3px)
|
||||
|
270
server.js
270
server.js
@ -6,11 +6,19 @@ var Moonboots = require('moonboots-express');
|
||||
var config = require('getconfig');
|
||||
var templatizer = require('templatizer');
|
||||
var async = require('async');
|
||||
var LDAP = require('LDAP');
|
||||
|
||||
String.prototype.capitalize = function() {
|
||||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||||
}
|
||||
|
||||
var app = express();
|
||||
var bodyParser = require('body-parser')
|
||||
var compression = require('compression');
|
||||
var serveStatic = require('serve-static');
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(compression());
|
||||
app.use(serveStatic(__dirname + '/public'));
|
||||
if (!config.isDev) {
|
||||
@ -43,6 +51,258 @@ app.get('/sounds/*', function (req, res) {
|
||||
res.redirect("./public" + req.baseUrl);
|
||||
});
|
||||
|
||||
function connectLDAP(req, cb) {
|
||||
|
||||
if (!config.ldap || !config.ldap.address || !config.ldap.base) {
|
||||
cb(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var ldapDN = 'uid=' + req.body.uid + ',' + config.ldap.base;
|
||||
var ldapPW = req.body.password;
|
||||
|
||||
var ldap = new LDAP({ uri: 'ldap://' + config.ldap.address, reconnect: false });
|
||||
|
||||
ldap.open(function(err) {
|
||||
if (err) {
|
||||
console.log("LDAP: Can not connect to server on ldap://" + config.ldap.address);
|
||||
cb(true);
|
||||
return;
|
||||
}
|
||||
|
||||
function closeCb(ldap) {
|
||||
ldap.close();
|
||||
console.log("LDAP: Disconnected");
|
||||
}
|
||||
|
||||
ldap.simplebind({ binddn: ldapDN, password: ldapPW }, function(err) {
|
||||
if (err) {
|
||||
console.log("LDAP: Can not connect to server with " + ldapDN);
|
||||
closeCb(ldap);
|
||||
cb(true);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("LDAP: Connected on ldap://" + config.ldap.address + " with " + ldapDN);
|
||||
|
||||
if (req.body.uid == config.server.admin && config.ldap.user && config.ldap.password) {
|
||||
console.log("LDAP: " + ldapDN + " is XMPP admin");
|
||||
|
||||
ldap.simplebind({ binddn: config.ldap.user, password: config.ldap.password }, function(err) {
|
||||
if (err) {
|
||||
console.log("LDAP: Can not connect to server with " + config.ldap.user);
|
||||
closeCb(ldap);
|
||||
cb(true);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("LDAP: Connected on ldap://" + config.ldap.address + " with " + config.ldap.user);
|
||||
cb(false, ldap, closeCb);
|
||||
});
|
||||
return;
|
||||
}
|
||||
cb(false, ldap, closeCb);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.post('/ldap/user/:id', function(req, res) {
|
||||
var dn = 'uid=' + req.params.id.toLowerCase() + ',' + config.ldap.base;
|
||||
console.log('LDAP: Save user informations (' + dn + ')');
|
||||
|
||||
connectLDAP(req, function (err, ldap, closeCb) {
|
||||
if (err === false) {
|
||||
|
||||
var changes = [];
|
||||
if (req.body.cn != undefined) changes.push({ op: 'replace', attr: 'cn', vals: [ req.body.cn ] });
|
||||
if (req.body.sn != undefined) changes.push({ op: 'replace', attr: 'sn', vals: [ req.body.sn ] });
|
||||
if (req.body.givenName != undefined) changes.push({ op: 'replace', attr: 'givenName', vals: [ req.body.givenName ] });
|
||||
if (req.body.displayName != undefined) changes.push({ op: 'replace', attr: 'displayName', vals: [ req.body.displayName ] });
|
||||
if (req.body.mail != undefined) changes.push({ op: 'replace', attr: 'mail', vals: [ req.body.mail ] });
|
||||
|
||||
ldap.modify(dn, changes, function (err) {
|
||||
if (err) {
|
||||
console.log('LDAP: Impossible to change user informations (' + dn + ')');
|
||||
console.log(err);
|
||||
res.type('application/javascript');
|
||||
res.send(false);
|
||||
|
||||
closeCb(ldap);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('LDAP: User informations saved (' + dn + ')');
|
||||
res.type('application/javascript');
|
||||
res.send(true);
|
||||
|
||||
closeCb(ldap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.post('/ldap/user/:id/password', function(req, res) {
|
||||
var dn = 'uid=' + req.params.id.toLowerCase() + ',' + config.ldap.base;
|
||||
console.log('LDAP: Change user password (' + dn + ')');
|
||||
|
||||
connectLDAP(req, function (err, ldap, closeCb) {
|
||||
if (err === false) {
|
||||
|
||||
var changes = [{ op: 'replace', attr: 'userPassword', vals: [ req.body.newPassword ] }];
|
||||
|
||||
ldap.modify(dn, changes, function (err) {
|
||||
if (err) {
|
||||
console.log('LDAP: Impossible to change user password (' + dn + ')');
|
||||
console.log(err);
|
||||
res.type('application/javascript');
|
||||
res.send(false);
|
||||
|
||||
closeCb(ldap);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('LDAP: User password changed (' + dn + ')');
|
||||
res.type('application/javascript');
|
||||
res.send(true);
|
||||
|
||||
closeCb(ldap);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ldap/users', function (req, res) {
|
||||
console.log('LDAP: Get users list');
|
||||
|
||||
connectLDAP(req, function (err, ldap, closeCb) {
|
||||
if (err === false) {
|
||||
var filter = config.ldap.filter;
|
||||
if (req.body.uid != config.server.admin) {
|
||||
var uid = 'uid=' + req.body.uid.toLowerCase();
|
||||
filter = '(&(' + filter + ')(' + uid + '))';
|
||||
}
|
||||
ldap.search({ base: config.ldap.base, filter: filter }, function(err, data) {
|
||||
var users = new Array();
|
||||
if (!err) {
|
||||
data.forEach(function(el) {
|
||||
var user = {
|
||||
id: el.uid[0],
|
||||
cn: el.cn ? el.cn[0] : '',
|
||||
sn: el.sn ? el.sn[0] : '',
|
||||
givenName: el.givenName ? el.givenName[0] : '',
|
||||
displayName: el.displayName ? el.displayName[0] : '',
|
||||
mail: el.mail ? el.mail[0] : '',
|
||||
objectClass: el.objectClass
|
||||
};
|
||||
users.push(user);
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log(err);
|
||||
}
|
||||
res.type('application/javascript');
|
||||
res.send(JSON.stringify(users));
|
||||
|
||||
console.log('LDAP: Users list sent');
|
||||
closeCb(ldap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.post('/ldap/users/add', function (req, res) {
|
||||
console.log('LDAP: Add a new user');
|
||||
|
||||
connectLDAP(req, function (err, ldap, closeCb) {
|
||||
if (err === false || !req.body.newUid) {
|
||||
var dn = 'uid=' + req.body.newUid.toLowerCase() + ',' + config.ldap.base;
|
||||
var attrs = [
|
||||
{ attr: 'objectClass', vals: [ 'organizationalPerson', 'person', 'inetOrgPerson'] },
|
||||
{ attr: 'cn', vals: [ req.body.newUid.capitalize() ] },
|
||||
{ attr: 'sn', vals: [ req.body.newUid.capitalize() ] },
|
||||
{ attr: 'givenName', vals: [ req.body.newUid.capitalize() ] },
|
||||
{ attr: 'displayName', vals: [ req.body.newUid.capitalize() ] },
|
||||
{ attr: 'userPassword', vals: [ req.body.newUid.toLowerCase() ] }
|
||||
];
|
||||
ldap.add(dn, attrs, function (err) {
|
||||
if (err) {
|
||||
console.log('LDAP: Impossible to add a new user (' + dn + ')');
|
||||
console.log(err);
|
||||
res.type('application/javascript');
|
||||
res.send(false);
|
||||
|
||||
closeCb(ldap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.ldap.group) {
|
||||
var changes = [
|
||||
{ op: 'add',
|
||||
attr: 'member',
|
||||
vals: [ dn ]
|
||||
}
|
||||
];
|
||||
ldap.modify(config.ldap.group, changes, function (err) {
|
||||
if (err) console.log(err);
|
||||
|
||||
console.log('LDAP: New user added (' + dn + ')');
|
||||
res.type('application/javascript');
|
||||
res.send(true);
|
||||
|
||||
closeCb(ldap);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.post('/ldap/users/delete', function (req, res) {
|
||||
console.log('LDAP: Remove a user');
|
||||
|
||||
connectLDAP(req, function (err, ldap, closeCb) {
|
||||
if (err === false || !req.body.removeUid) {
|
||||
var dn = 'uid=' + req.body.removeUid.toLowerCase() + ',' + config.ldap.base;
|
||||
ldap.remove(dn, function (err) {
|
||||
if (err) {
|
||||
console.log('LDAP: Impossible to remove this user (' + dn + ')');
|
||||
console.log(err);
|
||||
res.type('application/javascript');
|
||||
res.send(false);
|
||||
|
||||
closeCb(ldap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.ldap.group) {
|
||||
var changes = [
|
||||
{ op: 'delete',
|
||||
attr: 'member',
|
||||
vals: [ dn ]
|
||||
}
|
||||
];
|
||||
ldap.modify(config.ldap.group, changes, function (err) {
|
||||
if (err) console.log(err);
|
||||
|
||||
console.log('LDAP: User removed (' + dn + ')');
|
||||
res.type('application/javascript');
|
||||
res.send(true);
|
||||
|
||||
closeCb(ldap);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.get('/oauth/login', function (req, res) {
|
||||
res.redirect('https://apps.andyet.com/oauth/authorize?client_id=' + config.andyetAuth.id + '&response_type=token');
|
||||
});
|
||||
@ -85,14 +345,16 @@ var clientApp = new Moonboots({
|
||||
__dirname + '/clientapp/libraries/resampler.js',
|
||||
__dirname + '/clientapp/libraries/IndexedDBShim.min.js',
|
||||
__dirname + '/clientapp/libraries/sugar-1.2.1-dates.js',
|
||||
__dirname + '/clientapp/libraries/jquery.oembed.js'
|
||||
__dirname + '/clientapp/libraries/jquery.oembed.js',
|
||||
__dirname + '/clientapp/libraries/jquery-impromptu.js'
|
||||
],
|
||||
browserify: {
|
||||
debug: false
|
||||
},
|
||||
stylesheets: [
|
||||
__dirname + '/public/css/otalk.css',
|
||||
__dirname + '/public/css/jquery.oembed.css'
|
||||
__dirname + '/public/css/jquery.oembed.css',
|
||||
__dirname + '/public/css/jquery-impromptu.css'
|
||||
],
|
||||
beforeBuildJS: function () {
|
||||
if (config.isDev) {
|
||||
@ -138,5 +400,5 @@ clientApp.on('ready', function () {
|
||||
//}, app).listen(config.http.port);
|
||||
|
||||
app.listen(config.http.port, function () {
|
||||
console.log('demo.stanza.io running at: ' + config.http.baseUrl);
|
||||
})
|
||||
console.log('Otalk running at: ' + config.http.baseUrl);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user