Compare commits

...

34 Commits

Author SHA1 Message Date
Girish Ramakrishnan
9266302c4c Print graphite container id 2015-08-13 15:57:36 -07:00
Girish Ramakrishnan
755dce7bc4 fix graph issue finally 2015-08-13 15:54:27 -07:00
Girish Ramakrishnan
dd3e38ae55 Use latest graphite 2015-08-13 15:53:36 -07:00
Girish Ramakrishnan
9dfaa2d20f Create symlink in start.sh (and not container setup) 2015-08-13 15:36:21 -07:00
Girish Ramakrishnan
d6a4ff23e2 restart mysql in start.sh and not container setup 2015-08-13 15:16:01 -07:00
Girish Ramakrishnan
c2ab7e2c1f restart collectd 2015-08-13 15:04:57 -07:00
Girish Ramakrishnan
b9e4662dbb fix graphs again 2015-08-13 15:03:44 -07:00
Girish Ramakrishnan
10df0a527f Fix typo
remove thead_cache_size. it's dynamic anyways
2015-08-13 14:53:05 -07:00
Girish Ramakrishnan
9aad3688e1 Revert "Add hack to make graphs work with latest collectd"
This reverts commit a959418544.
2015-08-13 14:42:47 -07:00
Girish Ramakrishnan
e78dbcb5d4 limit threads and max connections 2015-08-13 14:42:36 -07:00
Girish Ramakrishnan
5e8cd09f51 Bump infra version 2015-08-13 14:22:39 -07:00
Girish Ramakrishnan
22f65a9364 Add hack to make graphs work with latest collectd
For some reason df-vda1 is not being collected by carbon. I have tried
all sorts of things and nothing works. This is a hack to get it working.
2015-08-13 13:47:44 -07:00
Girish Ramakrishnan
81b7432044 Turn off performance_schema in mysql 5.6 2015-08-13 13:47:44 -07:00
Girish Ramakrishnan
d49b90d9f2 Remove unused nodejs-disks 2015-08-13 10:34:06 -07:00
Girish Ramakrishnan
9face9cf35 systemd has moved around the cgroup hierarchy
https://github.com/docker/docker/issues/9902

There is some rationale here:
https://libvirt.org/cgroups.html
2015-08-13 10:21:33 -07:00
Girish Ramakrishnan
33ac34296e CpuShares is part of HostConfig 2015-08-12 23:47:35 -07:00
Girish Ramakrishnan
670ffcd489 Add warning 2015-08-12 19:52:23 -07:00
Girish Ramakrishnan
ec7b365c31 Use BASE_IMAGE as well 2015-08-12 19:51:44 -07:00
Girish Ramakrishnan
433d78c7ff Fix graphite version 2015-08-12 19:51:08 -07:00
Girish Ramakrishnan
ed041fdca6 Put image names in one place 2015-08-12 19:38:44 -07:00
Girish Ramakrishnan
b8e4ed2369 Use latest images 2015-08-12 19:19:58 -07:00
Johannes Zellner
d12f260d12 Prevent accessing oldConfig if it does not exist 2015-08-12 21:17:52 +02:00
Johannes Zellner
ba7989b57b Add ldap 'users' group 2015-08-12 17:38:31 +02:00
Johannes Zellner
88df410f5b Add ldap search unit tests 2015-08-12 15:31:54 +02:00
Johannes Zellner
2436db3b1f Add ldap memberof attribute 2015-08-12 15:31:44 +02:00
Johannes Zellner
d15874df63 Add initial ldap unit tests 2015-08-12 15:00:38 +02:00
Johannes Zellner
8fb90254cd Ensure the focus is properly set when restoring 2015-08-12 14:35:51 +02:00
Johannes Zellner
cbd712c20e Better integrate the progress bar 2015-08-12 14:32:20 +02:00
Johannes Zellner
8c004798f2 Improve login form layout 2015-08-12 14:23:13 +02:00
Johannes Zellner
c1b0cbe78d Give appstore hover a different color 2015-08-12 14:07:40 +02:00
Johannes Zellner
5ee72c8e98 Make webadmin pages a bit more streamlined with padding 2015-08-12 13:48:55 +02:00
Girish Ramakrishnan
c125cc17dc Apps must only get 50% less cpu than system processes when there is a contention for cpu 2015-08-11 17:00:48 -07:00
Johannes Zellner
18feff1bfb Increase installed app title 2015-08-11 15:22:30 +02:00
Johannes Zellner
f74f713bbd Hide geeky toolbar in apps icons 2015-08-11 13:04:50 +02:00
24 changed files with 503 additions and 121 deletions

17
npm-shrinkwrap.json generated
View File

@@ -1937,23 +1937,6 @@
"from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
},
"nodejs-disks": {
"version": "0.2.1",
"from": "https://registry.npmjs.org/nodejs-disks/-/nodejs-disks-0.2.1.tgz",
"resolved": "https://registry.npmjs.org/nodejs-disks/-/nodejs-disks-0.2.1.tgz",
"dependencies": {
"async": {
"version": "0.2.10",
"from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
},
"numeral": {
"version": "1.4.8",
"from": "https://registry.npmjs.org/numeral/-/numeral-1.4.8.tgz",
"resolved": "https://registry.npmjs.org/numeral/-/numeral-1.4.8.tgz"
}
}
},
"nodemailer": {
"version": "1.3.4",
"from": "https://registry.npmjs.org/nodemailer/-/nodemailer-1.3.4.tgz",

View File

@@ -43,7 +43,6 @@
"mysql": "^2.7.0",
"native-dns": "^0.7.0",
"node-uuid": "^1.4.3",
"nodejs-disks": "^0.2.1",
"nodemailer": "^1.3.0",
"nodemailer-smtp-transport": "^1.0.3",
"oauth2orize": "^1.0.1",

View File

@@ -3,4 +3,15 @@
# If you change the infra version, be sure to put a warning
# in the change log
INFRA_VERSION=4
INFRA_VERSION=8
# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
# These constants are used in the installer script as well
BASE_IMAGE=cloudron/base:0.3.1
MYSQL_IMAGE=cloudron/mysql:0.3.2
POSTGRESQL_IMAGE=cloudron/postgresql:0.3.1
MONGODB_IMAGE=cloudron/mongodb:0.3.1
REDIS_IMAGE=cloudron/redis:0.3.1 # if you change this, fix src/addons.js as well
MAIL_IMAGE=cloudron/mail:0.3.1
GRAPHITE_IMAGE=cloudron/graphite:0.3.3

View File

@@ -34,6 +34,9 @@ ln -sfF "${DATA_DIR}/collectd" /etc/collectd
unlink /etc/nginx 2>/dev/null || rm -rf /etc/nginx
ln -s "${DATA_DIR}/nginx" /etc/nginx
########## mysql
cp "${container_files}/mysql.cnf" /etc/mysql/mysql.cnf
########## Enable services
update-rc.d -f collectd defaults

View File

@@ -0,0 +1,7 @@
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/
# http://bugs.mysql.com/bug.php?id=68514
[mysqld]
performance_schema=OFF
max_connection=50

View File

@@ -56,6 +56,9 @@ echo "{ \"version\": \"${arg_version}\", \"boxVersionsUrl\": \"${arg_box_version
echo "Cleaning up snapshots"
find "${DATA_DIR}/snapshots" -mindepth 1 -maxdepth 1 | xargs --no-run-if-empty btrfs subvolume delete
# restart mysql to make sure it has latest config
service mysql restart
readonly mysql_root_password="password"
mysqladmin -u root -ppassword password password # reset default root password
mysql -u root -p${mysql_root_password} -e 'CREATE DATABASE IF NOT EXISTS box'
@@ -86,6 +89,10 @@ EOF
set_progress "28" "Setup collectd"
cp "${script_dir}/start/collectd.conf" "${DATA_DIR}/collectd/collectd.conf"
# collectd 5.4.1 has some bug where we simply cannot get it to create df-vda1
mkdir -p "${DATA_DIR}/graphite/whisper/collectd/localhost/"
vda1_id=$(blkid -s UUID -o value /dev/vda1)
ln -sfF "df-disk_by-uuid_${vda1_id}" "${DATA_DIR}/graphite/whisper/collectd/localhost/df-vda1"
service collectd restart
set_progress "30" "Setup nginx"

View File

@@ -193,12 +193,11 @@ LoadPlugin write_graphite
</Plugin>
<Plugin df>
Device "/dev/vda1"
Device "/dev/loop0"
Device "/dev/loop1"
FSType "tmpfs"
MountPoint "/dev"
ReportByDevice true
IgnoreSelected false
IgnoreSelected true
ValuesAbsolute true
ValuesPercentage true

View File

@@ -27,11 +27,13 @@ if [[ -n "${existing_containers}" ]]; then
fi
# graphite
docker run --restart=always -d --name="graphite" \
graphite_container_id=$(docker run --restart=always -d --name="graphite" \
-p 127.0.0.1:2003:2003 \
-p 127.0.0.1:2004:2004 \
-p 127.0.0.1:8000:8000 \
-v "${DATA_DIR}/graphite:/app/data" cloudron/graphite:0.3.1
-v "${DATA_DIR}/graphite:/app/data" \
"${GRAPHITE_IMAGE}")
echo "Graphite container id: ${graphite_container_id}"
# mail
mail_container_id=$(docker run --restart=always -d --name="mail" \
@@ -39,7 +41,7 @@ mail_container_id=$(docker run --restart=always -d --name="mail" \
-h "${arg_fqdn}" \
-e "DOMAIN_NAME=${arg_fqdn}" \
-v "${DATA_DIR}/box/mail:/app/data" \
cloudron/mail:0.3.0)
"${MAIL_IMAGE}")
echo "Mail container id: ${mail_container_id}"
# mysql
@@ -53,7 +55,7 @@ mysql_container_id=$(docker run --restart=always -d --name="mysql" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/mysql:/var/lib/mysql" \
-v "${DATA_DIR}/addons/mysql_vars.sh:/etc/mysql/mysql_vars.sh:ro" \
cloudron/mysql:0.3.0)
"${MYSQL_IMAGE}")
echo "MySQL container id: ${mysql_container_id}"
# postgresql
@@ -65,7 +67,7 @@ postgresql_container_id=$(docker run --restart=always -d --name="postgresql" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/postgresql:/var/lib/postgresql" \
-v "${DATA_DIR}/addons/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh:ro" \
cloudron/postgresql:0.3.0)
"${POSTGRESQL_IMAGE}")
echo "PostgreSQL container id: ${postgresql_container_id}"
# mongodb
@@ -77,7 +79,7 @@ mongodb_container_id=$(docker run --restart=always -d --name="mongodb" \
-h "${arg_fqdn}" \
-v "${DATA_DIR}/mongodb:/var/lib/mongodb" \
-v "${DATA_DIR}/addons/mongodb_vars.sh:/etc/mongodb_vars.sh:ro" \
cloudron/mongodb:0.3.0)
"${MONGODB_IMAGE}")
echo "Mongodb container id: ${mongodb_container_id}"
if [[ "${infra_version}" == "none" ]]; then

View File

@@ -665,7 +665,7 @@ function setupRedis(app, callback) {
name: 'redis-' + app.id,
Hostname: config.appFqdn(app.location),
Tty: true,
Image: 'cloudron/redis:0.3.0',
Image: 'cloudron/redis:0.3.1',
Cmd: null,
Volumes: {},
VolumesFrom: []

View File

@@ -201,8 +201,6 @@ function createContainer(app, callback) {
Tty: true,
Image: app.manifest.dockerImage,
Cmd: null,
Volumes: {},
VolumesFrom: [],
Env: env.concat(addonEnv),
ExposedPorts: exposedPorts
};
@@ -341,7 +339,8 @@ function startContainer(app, callback) {
RestartPolicy: {
"Name": "always",
"MaximumRetryCount": 0
}
},
CpuShares: 512 // relative to 1024 for system processes
};
var container = docker.getContainer(app.containerId);
@@ -672,7 +671,7 @@ function restore(app, callback) {
// note that configure is called after an infra update as well
function configure(app, callback) {
var locationChanged = app.oldConfig.location !== app.location;
var locationChanged = app.oldConfig ? app.oldConfig.location !== app.location : true;
async.series([
updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }),

View File

@@ -1,6 +1,6 @@
LoadPlugin "table"
<Plugin table>
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.stat">
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.stat">
Instance "<%= appId %>-memory"
Separator " \\n"
<Result>
@@ -10,7 +10,7 @@ LoadPlugin "table"
</Result>
</Table>
<Table "/sys/fs/cgroup/memory/docker/<%= containerId %>/memory.max_usage_in_bytes">
<Table "/sys/fs/cgroup/memory/system.slice/docker-<%= containerId %>.scope/memory.max_usage_in_bytes">
Instance "<%= appId %>-memory"
Separator "\\n"
<Result>
@@ -20,7 +20,7 @@ LoadPlugin "table"
</Result>
</Table>
<Table "/sys/fs/cgroup/cpuacct/docker/<%= containerId %>/cpuacct.stat">
<Table "/sys/fs/cgroup/cpuacct/system.slice/docker-<%= containerId %>.scope/cpuacct.stat">
Instance "<%= appId %>-cpu"
Separator " \\n"
<Result>

View File

@@ -24,6 +24,9 @@ var gLogger = {
fatal: console.error
};
var GROUP_USERS_DN = 'cn=users,ou=groups,dc=cloudron';
var GROUP_ADMINS_DN = 'cn=admin,ou=groups,dc=cloudron';
function start(callback) {
assert(typeof callback === 'function');
@@ -39,6 +42,9 @@ function start(callback) {
result.forEach(function (entry) {
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
var groups = [ GROUP_USERS_DN ];
if (entry.admin) groups.push(GROUP_ADMINS_DN);
var tmp = {
dn: dn.toString(),
attributes: {
@@ -49,7 +55,8 @@ function start(callback) {
mail: entry.email,
displayname: entry.username,
username: entry.username,
samaccountname: entry.username // to support ActiveDirectory clients
samaccountname: entry.username, // to support ActiveDirectory clients
memberof: groups
}
};
@@ -69,22 +76,32 @@ function start(callback) {
user.list(function (error, result){
if (error) return next(new ldap.OperationsError(error.toString()));
// we only have an admin group
var dn = ldap.parseDN('cn=admin,ou=groups,dc=cloudron');
var groups = [{
name: 'users',
admin: false
}, {
name: 'admins',
admin: true
}];
var tmp = {
dn: dn.toString(),
attributes: {
objectclass: ['group'],
cn: 'admin',
memberuid: result.filter(function (entry) { return entry.admin; }).map(function(entry) { return entry.id; })
groups.forEach(function (group) {
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
var tmp = {
dn: dn.toString(),
attributes: {
objectclass: ['group'],
cn: group.name,
memberuid: members.map(function(entry) { return entry.id; })
}
};
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && req.filter.matches(tmp.attributes)) {
res.send(tmp);
debug('ldap group send:', tmp);
}
};
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && req.filter.matches(tmp.attributes)) {
res.send(tmp);
debug('ldap group send:', tmp);
}
});
res.end();
});

View File

@@ -5,8 +5,10 @@
<div class="col-md-6 col-md-offset-3">
<div class="card">
<div class="row">
<div class="col-md-12">
<h1><img width="64" height="64" src="<%= applicationLogo %>"/> Login to <%= applicationName %> on <%= cloudronName %></h1>
<div class="col-md-12" style="text-align: center;">
<img width="128" height="128" src="<%= applicationLogo %>"/>
<h1>Login to <%= applicationName %> on <%= cloudronName %></h1>
<br/>
</div>
</div>
<br/>

View File

@@ -2,6 +2,10 @@
set -eu -o pipefail
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
source ${SOURCE_DIR}/setup/INFRA_VERSION
readonly mysqldatadir="/tmp/mysqldata-$(date +%s)"
readonly postgresqldatadir="/tmp/postgresqldata-$(date +%s)"
readonly mongodbdatadir="/tmp/mongodbdata-$(date +%s)"
@@ -20,7 +24,7 @@ start_postgresql() {
docker rm -f postgresql 2>/dev/null 1>&2 || true
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" -v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh cloudron/postgresql:0.3.0 >/dev/null
docker run -dtP --name=postgresql -v "${postgresqldatadir}:/var/lib/postgresql" -v /tmp/postgresql_vars.sh:/etc/postgresql/postgresql_vars.sh "${POSTGRESQL_IMAGE}" >/dev/null
}
start_mysql() {
@@ -36,7 +40,7 @@ start_mysql() {
docker rm -f mysql 2>/dev/null 1>&2 || true
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" -v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh cloudron/mysql:0.3.0 >/dev/null
docker run -dP --name=mysql -v "${mysqldatadir}:/var/lib/mysql" -v /tmp/mysql_vars.sh:/etc/mysql/mysql_vars.sh "${MYSQL_IMAGE}" >/dev/null
}
start_mongodb() {
@@ -52,7 +56,7 @@ start_mongodb() {
docker rm -f mongodb 2>/dev/null 1>&2 || true
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" -v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh cloudron/mongodb:0.3.0 >/dev/null
docker run -dP --name=mongodb -v "${mongodbdatadir}:/var/lib/mongodb" -v /tmp/mongodb_vars.sh:/etc/mongodb_vars.sh "${MONGODB_IMAGE}" >/dev/null
}
start_mysql

View File

@@ -2,22 +2,24 @@
set -eu
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source ${SOURCE_DIR}/setup/INFRA_VERSION
# reset sudo timestamp to avoid wrong success
sudo -k || sudo --reset-timestamp
# checks if all scripts are sudo access
scripts=("${SOURCE_DIR}/scripts/rmappdir.sh" \
"${SOURCE_DIR}/scripts/createappdir.sh" \
"${SOURCE_DIR}/scripts/reloadnginx.sh" \
"${SOURCE_DIR}/scripts/backupbox.sh" \
"${SOURCE_DIR}/scripts/backupapp.sh" \
"${SOURCE_DIR}/scripts/restoreapp.sh" \
"${SOURCE_DIR}/scripts/reboot.sh" \
"${SOURCE_DIR}/scripts/backupswap.sh" \
"${SOURCE_DIR}/scripts/collectlogs.sh" \
"${SOURCE_DIR}/scripts/reloadcollectd.sh")
scripts=("${SOURCE_DIR}/src/scripts/rmappdir.sh" \
"${SOURCE_DIR}/src/scripts/createappdir.sh" \
"${SOURCE_DIR}/src/scripts/reloadnginx.sh" \
"${SOURCE_DIR}/src/scripts/backupbox.sh" \
"${SOURCE_DIR}/src/scripts/backupapp.sh" \
"${SOURCE_DIR}/src/scripts/restoreapp.sh" \
"${SOURCE_DIR}/src/scripts/reboot.sh" \
"${SOURCE_DIR}/src/scripts/backupswap.sh" \
"${SOURCE_DIR}/src/scripts/collectlogs.sh" \
"${SOURCE_DIR}/src/scripts/reloadcollectd.sh")
for script in "${scripts[@]}"; do
if [[ $(sudo -n "${script}" --check 2>/dev/null) != "OK" ]]; then
@@ -37,23 +39,23 @@ if ! docker inspect girish/test:0.2.0 >/dev/null 2>/dev/null; then
exit 1
fi
if ! docker inspect cloudron/redis:0.3.0 >/dev/null 2>/dev/null; then
echo "docker pull cloudron/redis:0.3.0 for tests to run"
if ! docker inspect "${REDIS_IMAGE}" >/dev/null 2>/dev/null; then
echo "docker pull ${REDIS_IMAGE} for tests to run"
exit 1
fi
if ! docker inspect cloudron/mysql:0.3.0 >/dev/null 2>/dev/null; then
echo "docker pull cloudron/mysql:0.3.0 for tests to run"
if ! docker inspect "${MYSQL_IMAGE}" >/dev/null 2>/dev/null; then
echo "docker pull ${MYSQL_IMAGE} for tests to run"
exit 1
fi
if ! docker inspect cloudron/postgresql:0.3.0 >/dev/null 2>/dev/null; then
echo "docker pull cloudron/postgresql:0.3.0 for tests to run"
if ! docker inspect "${POSTGRESQL_IMAGE}" >/dev/null 2>/dev/null; then
echo "docker pull ${POSTGRESQL_IMAGE} for tests to run"
exit 1
fi
if ! docker inspect cloudron/mongodb:0.3.0 >/dev/null 2>/dev/null; then
echo "docker pull cloudron/mongodb:0.3.0 for tests to run"
if ! docker inspect "${MONGODB_IMAGE}" >/dev/null 2>/dev/null; then
echo "docker pull ${MONGODB_IMAGE} for tests to run"
exit 1
fi

263
src/test/ldap-test.js Normal file
View File

@@ -0,0 +1,263 @@
/* jslint node:true */
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
require('supererror', { splatchError: true});
var database = require('../database.js'),
expect = require('expect.js'),
EventEmitter = require('events').EventEmitter,
async = require('async'),
user = require('../user.js'),
config = require('../config.js'),
ldapServer = require('../ldap.js'),
ldap = require('ldapjs');
var USER_0 = {
username: 'foobar0',
password: 'password0',
email: 'foo0@bar.com'
};
var USER_1 = {
username: 'foobar1',
password: 'password1',
email: 'foo1@bar.com'
};
function setup(done) {
async.series([
database.initialize.bind(null),
database._clear.bind(null),
ldapServer.start.bind(null),
user.create.bind(null, USER_0.username, USER_0.password, USER_0.email, true, null),
user.create.bind(null, USER_1.username, USER_1.password, USER_1.email, false, USER_0)
], done);
}
function cleanup(done) {
database._clear(done);
}
describe('Ldap', function () {
before(setup);
after(cleanup);
describe('bind', function () {
it('fails for nonexisting user', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
client.bind('cn=doesnotexist,ou=users,dc=cloudron', 'password', function (error) {
expect(error).to.be.a(ldap.NoSuchObjectError);
done();
});
});
it('fails with wrong password', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
client.bind('cn=' + USER_0.username + ',ou=users,dc=cloudron', 'wrongpassword', function (error) {
expect(error).to.be.a(ldap.InvalidCredentialsError);
done();
});
});
it('succeeds', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
client.bind('cn=' + USER_0.username + ',ou=users,dc=cloudron', USER_0.password, function (error) {
expect(error).to.be(null);
done();
});
});
});
describe('search users', function () {
it ('fails for non existing tree', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '(&(l=Seattle)(email=*@foo.com))'
};
client.search('o=example', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
result.on('error', function (error) {
expect(error).to.be.a(ldap.NoSuchObjectError);
done();
});
result.on('end', function (result) {
done(new Error('Should not succeed. Status ' + result.status));
});
});
});
it ('succeeds with basic filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: 'objectcategory=person'
};
client.search('ou=users,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
expect(entries[0].username).to.equal(USER_0.username);
expect(entries[1].username).to.equal(USER_1.username);
done();
});
});
});
it ('succeeds with username wildcard filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '&(objectcategory=person)(username=foobar*)'
};
client.search('ou=users,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
expect(entries[0].username).to.equal(USER_0.username);
expect(entries[1].username).to.equal(USER_1.username);
done();
});
});
});
it ('succeeds with username filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '&(objectcategory=person)(username=' + USER_0.username + ')'
};
client.search('ou=users,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(1);
expect(entries[0].username).to.equal(USER_0.username);
expect(entries[0].memberof.length).to.equal(2);
done();
});
});
});
});
describe('search groups', function () {
it ('succeeds with basic filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: 'objectclass=group'
};
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
expect(entries[0].cn).to.equal('users');
expect(entries[0].memberuid.length).to.equal(2);
expect(entries[0].memberuid[0]).to.equal(USER_0.username);
expect(entries[0].memberuid[1]).to.equal(USER_1.username);
expect(entries[1].cn).to.equal('admins');
// if only one entry, the array becomes a string :-/
expect(entries[1].memberuid).to.equal(USER_0.username);
done();
});
});
});
it ('succeeds with cn wildcard filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '&(objectclass=group)(cn=*)'
};
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
expect(entries[0].cn).to.equal('users');
expect(entries[0].memberuid.length).to.equal(2);
expect(entries[0].memberuid[0]).to.equal(USER_0.username);
expect(entries[0].memberuid[1]).to.equal(USER_1.username);
expect(entries[1].cn).to.equal('admins');
// if only one entry, the array becomes a string :-/
expect(entries[1].memberuid).to.equal(USER_0.username);
done();
});
});
});
it('succeeds with memberuid filter', function (done) {
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '&(objectclass=group)(memberuid=' + USER_1.username + ')'
};
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(1);
expect(entries[0].cn).to.equal('users');
expect(entries[0].memberuid.length).to.equal(2);
done();
});
});
});
});
});

View File

@@ -120,6 +120,14 @@ html {
.grid-item {
padding: 10px;
min-width: 200px;
overflow: hidden;
}
.grid-item:hover .grid-item-bottom {
@media(min-width:768px) {
opacity: 1;
right: 10px;
}
}
.grid-item-content {
@@ -132,10 +140,39 @@ html {
padding: 10px 15px;
}
.grid-item-bottom {
.grid-item-top-title {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 24px;
}
.grid-item-bottom-mobile {
padding: 10px 15px;
border-top: 1px solid #ddd;
background-color: white
background-color: white;
@media(min-width:768px) {
display: none;
}
}
.grid-item-bottom {
display: none;
padding: 10px 15px;
border-top: 1px solid #ddd;
background-color: white;
@media(min-width:768px) {
display: block;
position: absolute;
top: 0;
right: -10px;
opacity: 0;
background-color: transparent;
transition: all 250ms;
}
}
// ----------------------------
@@ -195,17 +232,30 @@ html {
}
.appstore-category-link {
display: block;
padding: 10px;
margin: 0;
overflow: hidden;
color: black;
color: inherit;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
font-size: 18px;
&:hover,
&:focus,
&.category-active {
text-decoration: none;
background-color: white;
color: black;
}
&.category-active {
background-color: $navbar-default-link-hover-color;
color: white;
}
}
.appstore-category-missing {
padding: 10px;
background-color: white;
@@ -251,14 +301,6 @@ html {
color: $text-muted;
}
.appstore-category-link:hover,
.appstore-category-link:focus,
.appstore-category-link.category-active {
text-decoration: none;
background-color: $navbar-default-link-hover-color;
color: white;
}
.appstore-item-rating {
color: $navbar-default-link-hover-color;
}
@@ -358,12 +400,9 @@ html {
.grid-item-top .progress {
border-radius: 0;
box-shadown: none;
margin-left: -15px;
margin-right: -15px;
margin-bottom: -11px;
margin-top: 9px;
margin-top: 10px;
width: inherit;
height: 2px;
height: 10px;
}
.grid-item-top .progress-bar {
@@ -676,7 +715,7 @@ footer a {
background: #F7F7F7;
h1 {
margin-top: 0;
font-size: 33px;
}
.card {
@@ -842,6 +881,8 @@ $graphs-success-alt: lighten(#27CE65, 20%);
.support {
max-width: 600px;
h3 {
margin-top: 0;
margin-bottom: 20px;

View File

@@ -89,6 +89,8 @@
</div>
</div>
<br/>
<div style="max-width: 600px; margin: 0 auto;">
<div class="text-left">
<h1>Account</h1>

View File

@@ -54,7 +54,7 @@
</form>
</fieldset>
</div>
<div class="modal-footer">
<div class="modal-footer ">
<button type="button" class="btn btn-default" style="float: left;" ng-click="startApp(appConfigure.app)" ng-show="appConfigure.app.runState === 'stopped' && !appConfigure.runStateBusy && !(appConfigure.app | installationActive)"><i class="fa fa-play"></i> Start</button>
<button type="button" class="btn btn-default" style="float: left;" ng-show="appConfigure.app.runState !== 'stopped' && appConfigure.app.runState !== 'running' || appConfigure.runStateBusy && !(appConfigure.app | installationActive)" disabled ><i class="fa fa-refresh fa-spin"></i></button>
<button type="button" class="btn btn-default" style="float: left;" ng-click="stopApp(appConfigure.app)" ng-show="appConfigure.app.runState === 'running' && !appConfigure.runStateBusy && !(appConfigure.app | installationActive)"><i class="fa fa-pause"></i> Stop</button>
@@ -183,6 +183,9 @@
</script>
<div class="content">
<br/>
<div class="row animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
<div class="col-lg-12">
<h1>Installed Applications</h1>
@@ -201,11 +204,12 @@
</div>
<br/>
<div class="row">
<div class="col-xs-12 text-left">
<div style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">{{ app.location || app.fqdn }}</div>
<div class="col-xs-12 text-center">
<div class="grid-item-top-title">{{ app.location || app.fqdn }}</div>
<div class="text-muted" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">
{{ app | installationStateLabel }}
</div>
<br ng-hide="app | installationActive"/>
<div ng-show="app | installationActive">
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
@@ -214,31 +218,53 @@
</div>
</div>
</div>
</a>
<div class="grid-item-bottom" ng-show="user.admin">
<div class="row">
<div class="col-xs-4 text-left">
<a href="" ng-click="showRestore(app)" ng-show="(app | installError) === true">
<i class="fa fa-undo scale"></i>
</a>
<div class="grid-item-bottom-mobile" ng-show="user.admin">
<div class="row">
<div class="col-xs-4 text-left">
<a href="" ng-click="showRestore(app)" ng-show="(app | installError) === true">
<i class="fa fa-undo scale"></i>
</a>
<a href="" ng-click="showConfigure(app)" ng-show="(app | installSuccess) == true">
<i class="fa fa-wrench scale"></i>
</a>
</div>
<div class="col-xs-4 text-center">
<!-- we check the version here because the box updater does not know when an app gets updated -->
<a href="" ng-click="showUpdate(app)" class="ng-hide animateMe" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
<i class="fa fa-arrow-up text-success scale"></i>
</a>
</div>
<div class="col-xs-4 text-right">
<a href="" ng-click="showUninstall(app)">
<i class="fa fa-remove scale"></i>
</a>
<a href="" ng-click="showConfigure(app)" ng-show="(app | installSuccess) == true">
<i class="fa fa-wrench scale"></i>
</a>
</div>
<div class="col-xs-4 text-center">
<!-- we check the version here because the box updater does not know when an app gets updated -->
<a href="" ng-click="showUpdate(app)" class="ng-hide animateMe" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
<i class="fa fa-arrow-up text-success scale"></i>
</a>
</div>
<div class="col-xs-4 text-right">
<a href="" ng-click="showUninstall(app)">
<i class="fa fa-remove scale"></i>
</a>
</div>
</div>
</div>
</div>
<div class="grid-item-bottom" ng-show="user.admin">
<br/>
<div>
<a href="" ng-click="showUninstall(app)"><i class="fa fa-remove scale"></i></a>
</div>
<div ng-show="(app | installError) === true">
<a href="" ng-click="showRestore(app)"><i class="fa fa-undo scale"></i></a>
</div>
<div ng-show="(app | installSuccess) == true">
<a href="" ng-click="showConfigure(app)"><i class="fa fa-wrench scale"></i></a>
</div>
<!-- we check the version here because the box updater does not know when an app gets updated -->
<div ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
<a href="" ng-click="showUpdate(app)"><i class="fa fa-arrow-up text-success scale"></i></a>
</div>
<br/>
</div>
</a>
</div>
</div>
</div>

View File

@@ -334,7 +334,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
};
// setup all the dialog focus handling
['appConfigureModal', 'appUninstallModal', 'appUpdateModal'].forEach(function (id) {
['appConfigureModal', 'appUninstallModal', 'appUpdateModal', 'appRestoreModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find("[autofocus]:first").focus();
});

View File

@@ -1,4 +1,6 @@
<br/>
<div class="row">
<div class="col-md-12">
<h1>Graphs</h1>

View File

@@ -107,6 +107,14 @@
</div>
</div>
<br/>
<div style="max-width: 600px; margin: 0 auto;">
<div class="text-left">
<h1>Settings</h1>
</div>
</div>
<div style="max-width: 600px; margin: 0 auto;" ng-show="user.admin">
<div class="text-left">
<h3>Backups</h3>

View File

@@ -1,4 +1,6 @@
<div class="content support">
<br/>
<div>
<div class="text-left">
<h1>Support</h1>

View File

@@ -80,6 +80,9 @@
</div>
<div class="content">
<br/>
<div>
<div class="text-left">
<h1>Users <button class="btn btn-primary btn-outline pull-right" data-toggle="modal" data-target="#userAddModal"><i class="fa fa-user-plus"></i> New User</button></h1>