Compare commits

..

47 Commits

Author SHA1 Message Date
girish@cloudron.io 09a88e6a1c Version 0.6.2 changes 2016-01-15 18:12:20 -08:00
girish@cloudron.io 28baef8929 Go back to using docker exec in cloudron exec
The main issue is that multiple cloudron exec sessions do not
share the same rootfs. Which makes it annoying to debug.

We also have some nginx timeout which drops you out of exec
now and then resulting in loss of all state.
2016-01-15 15:24:46 -08:00
girish@cloudron.io 9b061a4c7c make the command work 2016-01-15 14:50:13 -08:00
girish@cloudron.io 0b542dfbdf Pause app container in developmentMode
This allows us to share the network namespace with the app container
2016-01-15 14:34:15 -08:00
girish@cloudron.io d3b039ebd8 support developmentMode flag
- disables readonly rootfs
- disables memory limit
2016-01-15 11:28:43 -08:00
Johannes Zellner c22924eed7 Use user.js instead of userdb.js in mailer 2016-01-15 16:33:13 +01:00
Johannes Zellner 033ccb121f Add tests for user.getAllAdmins() 2016-01-15 16:33:13 +01:00
Johannes Zellner ecd91e8f2a Add user.getAllAdmins() 2016-01-15 16:33:13 +01:00
girish@cloudron.io 1cdb954967 0.6.1 changes 2016-01-14 13:30:08 -08:00
girish@cloudron.io f309f87f55 use no-reply for naked domain apps 2016-01-14 12:56:35 -08:00
girish@cloudron.io 989ab3094d Set initial progress so that tools can wait on it 2016-01-14 11:34:49 -08:00
girish@cloudron.io 70ac18d139 add internal route to update the cloudron
need to way to trigger updates of cloudron using the caas tool
2016-01-14 11:13:02 -08:00
girish@cloudron.io 8f43236e2e adjust update mail text 2016-01-14 11:02:05 -08:00
girish@cloudron.io 38416d46a6 insist on node 4.1.1
this is probably FUD but I think we get npm rebuild issues when
trying to rebuild packages of a newer node to an older node.
2016-01-14 10:55:53 -08:00
girish@cloudron.io fb96b00922 just keep rebuilding
Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:03:27 GMT installer:installer provision (stdout):
Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: > bson@0.2.22 install
/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/mongodb/node_modules/bson

Jan 14 07:03:27 rudolf.cloudron.me server.js[541]: > (node-gyp rebuild
2> builderror.log) || (exit 0)
Jan 14 07:03:31 rudolf.cloudron.me ntpdate[1344]: step time server
91.189.89.199 offset 0.000661 sec
Jan 14 07:03:31 rudolf.cloudron.me systemd[1]: Time has been changed
Jan 14 07:03:44 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=79.174.70.237 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=248
ID=54321 PROTO=TCP SPT=49152 DPT=22 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 14 07:04:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61522 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:04:08 GMT installer:installer provision (stdout):
Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: > kerberos@0.0.11
install
/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/mongodb/node_modules/kerberos

Jan 14 07:04:08 rudolf.cloudron.me server.js[541]: > (node-gyp rebuild
2> builderror.log) || (exit 0)
Jan 14 07:04:47 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=58.218.205.83 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=112
ID=256 PROTO=TCP SPT=49127 DPT=5555 WINDOW=512 RES=0x00 SYN URGP=0
Jan 14 07:05:18 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 91.207.136.55:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:05:28 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 194.190.168.1:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:05:49 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=218.77.79.38 DST=178.62.202.80 LEN=40 TOS=0x00 PREC=0x00 TTL=240
ID=54321 PROTO=TCP SPT=44094 DPT=3306 WINDOW=65535 RES=0x00 SYN URGP=0
Jan 14 07:06:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61523 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout):
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: > sqlite3@3.1.1
install /tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/sqlite3
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: > node-pre-gyp
install --fallback-to-build
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout): [sqlite3] Success:
"/tmp/box-src-a4tklr/node_modules/db-migrate/node_modules/sqlite3/lib/binding/node-v46-linux-x64/node_sqlite3.node"
already installed
Jan 14 07:06:21 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:21 GMT installer:installer provision (stdout): Pass
--update-binary to reinstall or --build-from-source to recompile
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:06:23 GMT installer:installer provision (stdout):
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: >
dtrace-provider@0.2.8 install
/tmp/box-src-a4tklr/node_modules/ldapjs/node_modules/dtrace-provider
Jan 14 07:06:23 rudolf.cloudron.me server.js[541]: > node-gyp rebuild
Jan 14 07:07:47 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 91.206.16.3:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:07:57 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 46.254.216.12:123 (2.ubuntu.pool.ntp.org).
Jan 14 07:08:02 rudolf.cloudron.me kernel: IPTables Packet Dropped:
IN=eth0 OUT= MAC=04:01:9a:dd:a9:01:84:b5:9c:fa:08:30:08:00
SRC=124.6.36.197 DST=178.62.202.80 LEN=638 TOS=0x00 PREC=0x00 TTL=59
ID=61524 DF PROTO=TCP SPT=443 DPT=58535 WINDOW=95 RES=0x00 ACK PSH URGP=0
Jan 14 07:08:08 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 85.255.214.66:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:18 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 83.98.201.134:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:28 rudolf.cloudron.me systemd-timesyncd[448]: Timed out
waiting for reply from 194.171.167.130:123 (3.ubuntu.pool.ntp.org).
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): WARN
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): install
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  got an error,
rolling back install

<...>

07:08:31 GMT installer:installer provision (stderr):  Error: connect
ETIMEDOUT 104.20.23.46:443
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
Object.exports._errnoException (util.js:837:11)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
exports._exceptionWithHostPort (util.js:860:20)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): stack
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):      at
TCPConnectWrap.afterConnect [as oncomplete] (net.js:1060:14)
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): System Linux
3.19.0-31-generic
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! command
"/usr/local/node-4.1.1/bin/node"
"/usr/local/node-4.1.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js"
"rebuild"
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! cwd
/tmp/box-src-a4tklr/node_modules/ldapjs/node_modules/dtrace-provider
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): node -v
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  v4.1.1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): gyp
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): node-gyp -v
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  v3.0.3
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: gyp ERR! not ok
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  Linux
3.19.0-31-generic
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): argv
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
"/usr/local/node-4.1.1/bin/node" "/usr/bin/npm" "rebuild"
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm ERR! node v4.1.1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! npm  v2.14.4
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! code ELIFECYCLE
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
dtrace-provider@0.2.8 install: `node-gyp rebuild`
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Exit status 1
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Failed at
the dtrace-provider@0.2.8 install script 'node-gyp rebuild'.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  This is most
likely a problem with the dtrace-provider package,
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! not with npm
itself.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! Tell the
author that this fails on your system:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!     node-gyp
rebuild
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! You can get
their info via:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!     npm
owner ls dtrace-provider
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR! There is
likely additional logging output above.
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): npm
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr): ERR!
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision (stderr):  Please include the
following file with any support request:
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: npm ERR!
/tmp/box-src-a4tklr/npm-debug.log
Jan 14 07:08:31 rudolf.cloudron.me sudo[1284]: pam_unix(sudo:session):
session closed for user root
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: Thu, 14 Jan 2016
07:08:31 GMT installer:installer provision : child process exited. code:
1 signal: 0
Jan 14 07:08:31 rudolf.cloudron.me server.js[541]: [Error: Exited with
code 1]
2016-01-14 10:51:21 -08:00
Johannes Zellner 0601ea2f39 Show actionable notification in case we miss aws credentials
Fixes #553
2016-01-14 16:01:10 +01:00
Johannes Zellner a92d4f2af7 add some comments on how to use notifications 2016-01-14 15:56:23 +01:00
Johannes Zellner 658305c969 Tweak the notification appearance 2016-01-14 15:54:32 +01:00
Johannes Zellner 8aed2be19b Support action scopes 2016-01-14 15:54:14 +01:00
Johannes Zellner 263f6e49d8 Use custom notification template 2016-01-14 15:53:47 +01:00
Johannes Zellner d822e38016 Ensure templates are available 2016-01-14 15:53:32 +01:00
Johannes Zellner 6cf78f19bb Add custom notification template to make actionable 2016-01-14 15:53:16 +01:00
Johannes Zellner 4a3319406c Add Client api to show notifications 2016-01-14 15:08:27 +01:00
Johannes Zellner cb4418b973 Set default values for the notifications 2016-01-14 15:07:41 +01:00
Johannes Zellner 5a797124b3 Update the angular notification library 2016-01-14 15:07:10 +01:00
Johannes Zellner 63430fbce6 Adjust update email text to let the user know about auto updates
So far the emails make no sense in case the user only clicks the update
link after the Cloudron has already auto updated. We might want to
adjust the text, once the user can disable auto updates, or set the
preferred update time.
2016-01-14 13:55:02 +01:00
girish@cloudron.io bc2085139e ensure no trailing slash in redis password
Fixes #539
2016-01-13 19:42:28 -08:00
girish@cloudron.io f98c710f5b use password generator module 2016-01-13 19:02:15 -08:00
girish@cloudron.io eb7101deff setupToken is property of wizard
fixes #559
2016-01-13 18:41:08 -08:00
girish@cloudron.io 826f50da7e expose wizard in SetupController 2016-01-13 18:04:07 -08:00
girish@cloudron.io 4e94c8ea56 updateContact gets 202 and not 200 2016-01-13 16:46:01 -08:00
girish@cloudron.io 3120eca721 status api should set provider 2016-01-13 16:09:36 -08:00
girish@cloudron.io 26c9bcbc28 fix this and that 2016-01-13 15:00:33 -08:00
girish@cloudron.io 7a2e73a5d6 acme: update account with owner email
fixes #544
2016-01-13 14:21:59 -08:00
girish@cloudron.io cd35ab5932 acme: update contact information before getting a cert
part of #544

there were two approaches considered:
1. pipe through owner email from appstore. this requires to save this
   value in settingsdb and we need to remember this in case user changes
   the email. another issue is that selfhost installer tooling needs to
   require this new value.

2. simply update owner email each time. this is the chosen approach.
2016-01-13 14:06:31 -08:00
girish@cloudron.io efaacdb534 Add getOwner 2016-01-13 12:37:56 -08:00
girish@cloudron.io 5eb3c208f1 allow email to be configured 2016-01-13 12:15:27 -08:00
Johannes Zellner 8347b62c1b Mention lets encrypt in the ssl settings 2016-01-13 16:24:15 +01:00
Johannes Zellner 48c3c7b4dc Also hide the webadmin domain cert ui 2016-01-13 16:17:46 +01:00
Johannes Zellner faefe078af Make links in app description go to a new page 2016-01-13 16:16:40 +01:00
Johannes Zellner 66eb0481b5 Hide app per app certificate upload fields
Fixes #555
2016-01-13 16:00:55 +01:00
Johannes Zellner 0a0fc130d4 Fix linter errors 2016-01-13 16:00:55 +01:00
Johannes Zellner 44afd7b657 No need to check for version equality for the update badge 2016-01-13 14:50:05 +01:00
Johannes Zellner 165b572a5f Fixup the app grid icon action layout 2016-01-13 14:49:00 +01:00
Johannes Zellner 1a30e622cc Only register app updates for apps where the available version is actually bigger
Fixes #533
2016-01-13 14:48:52 +01:00
Johannes Zellner aec3238e42 Add changlog for 0.5.0 2016-01-13 11:51:21 +01:00
Johannes Zellner 249868dba7 Add CHANGES file 2016-01-13 11:51:13 +01:00
38 changed files with 1025 additions and 426 deletions
+374
View File
@@ -0,0 +1,374 @@
[0.0.1]
- Hot Chocolate
[0.0.2]
- Hotfix appstore ui in webadim
[0.0.3]
- Tall Pike
[0.0.4]
- This will be 0.0.4 changes
[0.0.5]
- App install/configure route fixes
[0.0.6]
- Not sure what happenned here
[0.0.7]
- resetToken is now sent as part of create user
- Same as 0.0.7 which got released by mistake
[0.0.8]
- Manifest changes
[0.0.9]
- Fix app restore
- Fix backup issues
[0.0.10]
- Unknown orchestra
[0.0.11]
- Add ldap addon
[0.0.12]
- Support OAuth2 state
[0.0.13]
- Use docker image from cloudron repository
[0.0.14]
- Improve setup flow
[0.0.15]
- Improved Appstore view
[0.0.16]
- Improved Backup approach
[0.0.17]
- Upgrade testing
- App auto updates
- Usage graphs
[0.0.18]
- Rework backups and updates
[0.0.19]
- Graphite fixes
- Avatar and Cloudron name support
[0.0.20]
- Apptask fixes
- Chrome related fixes
[0.0.21]
- Increase nginx hostname size to 64
[0.0.22]
- Testing the e2e tests
[0.0.23]
- Better error status page
- Fix updater and backup progress reporting
- New avatar set
- Improved setup wizard
[0.0.24]
- Hotfix the ldap support
[0.0.25]
- Add support page
- Really fix ldap issues
[0.0.26]
- Add configurePath support
[0.0.27]
- Improved log collector
[0.0.28]
- Improve app feedback
- Restyle login page
[0.0.29]
- Update to ubuntu 15.04
[0.0.30]
- Move to docker 1.7
[0.0.31]
- WARNING: This update restarts your containers
- System processes are prioritized over apps
- Add ldap group support
[0.0.32]
- MySQL addon update
[0.0.33]
- Fix graphs
- Fix MySQL 5.6 memory usage
[0.0.34]
- Correctly mark apps pending for approval
[0.0.35]
- Fix ldap admin group username
[0.0.36]
- Fix restore without backup
- Optimize image deletion during updates
- Add memory accounting
- Restrict access to metadata from containers
[0.0.37]
- Prepare for Selfhosting 1. part
- Use userData instead of provisioning calls
[0.0.38]
- Account for Ext4 reserved block when partitioning disk
[0.0.39]
- Move subdomain management to the cloudron
[0.0.40]
- Add journal limit
- Fix reprovisioning on reboot
- Fix subdomain management during startup
[0.0.41]
- Finally bring things to a sane state
[0.0.42]
- Parallel apptask
[0.0.43]
- Move to systemd
[0.0.44]
- Fix apptask concurrency bug
[0.0.45]
- Retry subdomain registration
[0.0.46]
- Fix app update email notification
[0.0.47]
- Ensure box code quits within 5 seconds
[0.0.48]
- Styling fixes
- Improved session handling
[0.0.49]
- Fix app autoupdate logic
[0.0.50]
- Use domainmanagement via CaaS
[0.0.51]
- Fix memory management
[0.0.52]
- Restrict addons memory
- Get nofication about container OOMs
[0.0.53]
- Restrict addons memory
- Get notification about container OOMs
- Add retry to subdomain logic
[0.0.54]
- OAuth Proxy now uses internal port forwarding
[0.0.55]
- Setup cloudron timezone based on droplet region
[0.0.56]
- Use correct timezone in updater
[0.0.57]
- Fix systemd logging issues
[0.0.58]
- Ensure backups of failed apps are retained across archival cycles
[0.0.59]
- Installer API fixes
[0.0.60]
- Do full box backup on updates
[0.0.61]
- Track update notifications to inform admin only once
[0.0.62]
- Export bind dn and password from LDAP addon
[0.0.63]
- Fix creation of TXT records
[0.0.64]
- Stop apps in a retired cloudron
- Retry downloading application on failure
[0.0.65]
- Do not send crash mails for apps in development
[0.0.66]
- Readonly application and addon containers
[0.0.67]
- Fix email notifications
- Fix bug when restoring from certain backups
[0.0.68]
- Update graphite image
- Add simpleauth addon support
[0.0.69]
- Support newer manifest format
- Fix app listing rendering in chrome
- Fix redis backup across upgrades
[0.0.70]
- Retry app download on error
[0.0.71]
- Fix oauth and simple auth login
[0.0.72]
- Cleanup application volumes periodically
- New application logging design
[0.0.73]
- Update SSL certificate
[0.0.74]
- Support singleUser apps
[0.0.75]
- scheduler addon
[0.0.76]
- DNS Sync fixes
- Show warning to user when memory limit reached
[0.0.77]
- Do not set hostname in app containers
[0.0.78]
- Support custom domains
[0.0.79]
- Move SSH Port
[0.0.80]
- Use journalctl for container logs
[0.1.0]
- Wait for configuration changes before starting Cloudron
[0.1.1]
- Ensure dns config for all cloudrons
[0.1.2]
- Make email work again
- Add DKIM keys for custom domains
[0.1.3]
- Storage backend
[0.1.4]
- CaaS Backup configuration fix
[0.1.5]
- Use correct tokens for DNS backend
[0.1.6]
- Add hook to determine the api server of the box
- Fix crash notification
[0.2.0]
- New cloudron exec implementation
[0.2.1]
- Update to node 4.1.1
- Fix certification installation with custom domains
[0.2.2]
- Better debug output
- Retry more times if docker registry goes down
[0.3.0]
- Update SSH keys
- Allow bigger manifest files
[0.4.0]
- Update to docker 1.9.0
[0.4.1]
- Fix scheduler crash
- Crucial OAuth fixes
[0.4.2]
- Fix crash when reporting backup error
- Allow larger manifests
[0.4.3]
- Fix cloudron exec
[0.4.4]
- Initial Lets Encrypt integration
[0.4.5]
- Fixup nginx configuration to allow dynamic certificates
[0.4.6]
- LetsEncrypt integration for custom domains
- Rate limit crash emails
[0.5.0]
- Enable staging Lets Encrypt Integration
[0.5.1]
- Display error dialog for app installation errors
- Enable prod Lets Encrypt Integration
- Handle apptask crashes correctly
[0.5.2]
- Fix apphealthtask crash
- Use cgroup fs driver instead of systemd cgroup driver in docker
[0.5.3]
- Changes for e2e testing
[0.5.4]
- Fix bug in LE server selection
[0.5.5]
- Scheduler redesign
- Fix journalctl logging
[0.5.6]
- Prepare for selfhosting option
[0.5.7]
- Move app images off the btrfs subvolume
[0.6.0]
- Consolidate code repositories
[0.6.1]
- Use no-reply as email from address for apps in naked domains
- Update Lets Encrypt account with owner email when available
- Fix email templates to indicate auto update
- Add notification UI
[0.6.2]
- Fix `cloudron exec` container to have same namespaces as app
- Add developmentMode to manifest
+6 -1
View File
@@ -102,7 +102,7 @@ gulp.task('js-update', function () {
// HTML
// --------------
gulp.task('html', ['html-views', 'html-update'], function () {
gulp.task('html', ['html-views', 'html-update', 'html-templates'], function () {
return gulp.src('webadmin/src/*.html').pipe(gulp.dest('webadmin/dist'));
});
@@ -114,6 +114,10 @@ gulp.task('html-views', function () {
return gulp.src('webadmin/src/views/**/*.html').pipe(gulp.dest('webadmin/dist/views'));
});
gulp.task('html-templates', function () {
return gulp.src('webadmin/src/templates/**/*.html').pipe(gulp.dest('webadmin/dist/templates'));
});
// --------------
// CSS
// --------------
@@ -143,6 +147,7 @@ gulp.task('watch', ['default'], function () {
gulp.watch(['webadmin/src/img/*'], ['images']);
gulp.watch(['webadmin/src/**/*.html'], ['html']);
gulp.watch(['webadmin/src/views/*.html'], ['html-views']);
gulp.watch(['webadmin/src/templates/*.html'], ['html-templates']);
gulp.watch(['webadmin/src/js/update.js'], ['js-update']);
gulp.watch(['webadmin/src/js/error.js'], ['js-error']);
gulp.watch(['webadmin/src/js/setup.js', 'webadmin/src/js/client.js'], ['js-setup']);
+6 -1
View File
@@ -40,7 +40,12 @@ while true; do
echo "Failed to download source tarball, trying again"
sleep 5
done
(cd "${box_src_tmp_dir}" && npm rebuild)
while true; do
# for reasons unknown, the dtrace package will fail. but rebuilding second time will work
if cd "${box_src_tmp_dir}" && npm rebuild; then break; fi
echo "Failed to rebuild, trying again"
sleep 5
done
if [[ "${is_update}" == "yes" ]]; then
echo "Setting up update splash screen"
+310 -310
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -18,7 +18,7 @@
"aws-sdk": "^2.1.46",
"body-parser": "^1.13.1",
"bytes": "^2.1.0",
"cloudron-manifestformat": "^2.0.0",
"cloudron-manifestformat": "^2.2.0",
"connect-ensure-login": "^0.1.1",
"connect-lastmile": "0.0.13",
"connect-timeout": "^1.5.0",
+5
View File
@@ -49,6 +49,11 @@ if ! $(cd "${SOURCE_DIR}" && git diff --exit-code >/dev/null); then
exit 1
fi
if [[ "$(node --version)" != "v4.1.1" ]]; then
echo "This script requires node 4.1.1"
exit 1
fi
version=$(cd "${SOURCE_DIR}" && git rev-parse "${commitish}")
bundle_dir=$(mktemp -d -t box 2>/dev/null || mktemp -d box-XXXXXXXXXX --tmpdir=$TMPDIR)
[[ -z "$bundle_file" ]] && bundle_file="${TMPDIR}/box-${version}.tar.gz"
+4 -2
View File
@@ -388,10 +388,12 @@ function setupSendMail(app, options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
var username = app.location ? app.location + '-app' : 'no-reply'; // use no-reply for bare domains
var env = [
'MAIL_SMTP_SERVER=mail',
'MAIL_SMTP_PORT=2500', // if you change this, change the mail container
'MAIL_SMTP_USERNAME=' + (app.location || app.id) + '-app', // use app.id for bare domains
'MAIL_SMTP_USERNAME=' + username,
'MAIL_DOMAIN=' + config.fqdn()
];
@@ -764,7 +766,7 @@ function setupRedis(app, options, callback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
var redisPassword = generatePassword(64, false /* memorable */);
var redisPassword = generatePassword(64, false /* memorable */, /[\w\d_]/); // ensure no / in password for being sed friendly (and be uri friendly)
var redisVarsFile = path.join(paths.ADDON_CONFIG_DIR, 'redis-' + app.id + '_vars.sh');
var redisDataDir = path.join(paths.DATA_DIR, app.id + '/redis');
+16 -27
View File
@@ -648,42 +648,31 @@ function exec(appId, options, callback) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
var createOptions = {
var container = docker.connection.getContainer(app.containerId);
var execOptions = {
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
OpenStdin: true,
StdinOnce: false,
Tty: true
Tty: true,
Cmd: cmd
};
docker.createSubcontainer(app, app.id + '-exec-' + Date.now(), cmd, createOptions, function (error, container) {
container.exec(execOptions, function (error, exec) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
container.attach({ stream: true, stdin: true, stdout: true, stderr: true }, function (error, stream) {
var startOptions = {
Detach: false,
Tty: true,
stdin: true // this is a dockerode option that enabled openStdin in the modem
};
exec.start(startOptions, function(error, stream) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
docker.startContainer(container.id, function (error) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
if (options.rows && options.columns) {
exec.resize({ h: options.rows, w: options.columns }, function (error) { if (error) debug('Error resizing console', error); });
}
if (options.rows && options.columns) {
container.resize({ h: options.rows, w: options.columns }, NOOP_CALLBACK);
}
var deleteContainer = once(docker.deleteContainer.bind(null, container.id, NOOP_CALLBACK));
container.wait(function (error) {
if (error) debug('Error waiting on container', error);
debug('exec: container finished', container.id);
deleteContainer();
});
stream.close = deleteContainer;
callback(null, stream);
});
return callback(null, stream);
});
});
});
+34 -16
View File
@@ -57,7 +57,7 @@ function Acme(options) {
this.caOrigin = options.prod ? CA_PROD : CA_STAGING;
this.accountKeyPem = null; // Buffer
this.email = options.email;
this.chainPem = options.prod ? safe.fs.readFileSync(__dirname + '/lets-encrypt-x1-cross-signed.pem.txt') : new Buffer('');
}
@@ -125,26 +125,50 @@ Acme.prototype.sendSignedRequest = function (url, payload, callback) {
});
};
Acme.prototype.registerUser = function (email, callback) {
assert.strictEqual(typeof email, 'string');
Acme.prototype.updateContact = function (registrationUri, callback) {
assert.strictEqual(typeof registrationUri, 'string');
assert.strictEqual(typeof callback, 'function');
debug('updateContact: %s %s', registrationUri, this.email);
// https://github.com/ietf-wg-acme/acme/issues/30
var payload = {
resource: 'reg',
contact: [ 'mailto:' + this.email ],
agreement: LE_AGREEMENT
};
var that = this;
this.sendSignedRequest(registrationUri, JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering user: ' + error.message));
if (result.statusCode !== 202) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to update contact. Expecting 202, got %s %s', result.statusCode, result.text)));
debug('updateContact: contact of user updated to %s', that.email);
callback();
});
};
Acme.prototype.registerUser = function (callback) {
assert.strictEqual(typeof callback, 'function');
var payload = {
resource: 'new-reg',
contact: [ 'mailto:' + email ],
contact: [ 'mailto:' + this.email ],
agreement: LE_AGREEMENT
};
debug('registerUser: %s', email);
debug('registerUser: %s', this.email);
var that = this;
this.sendSignedRequest(this.caOrigin + '/acme/new-reg', JSON.stringify(payload), function (error, result) {
if (error) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, 'Network error when registering user: ' + error.message));
if (result.statusCode === 409) return callback(new AcmeError(AcmeError.ALREADY_EXISTS, result.body.detail));
if (result.statusCode === 409) return that.updateContact(result.headers.location, callback); // already exists
if (result.statusCode !== 201) return callback(new AcmeError(AcmeError.EXTERNAL_ERROR, util.format('Failed to register user. Expecting 201, got %s %s', result.statusCode, result.text)));
debug('registerUser: registered user %s', email);
debug('registerUser: registered user %s', that.email);
callback();
callback(null);
});
};
@@ -350,12 +374,6 @@ Acme.prototype.acmeFlow = function (domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
// we cannot use admin@fqdn because the user might not have set it up.
// we cannot use owner email because we don't have it yet (the admin cert is fetched before activation)
// one option is to update the owner email when a second cert is requested (https://github.com/ietf-wg-acme/acme/issues/30)
var email = 'admin@cloudron.io';
if (!fs.existsSync(paths.ACME_ACCOUNT_KEY_FILE)) {
debug('getCertificate: generating acme account key on first run');
this.accountKeyPem = safe.child_process.execSync('openssl genrsa 4096');
@@ -368,8 +386,8 @@ Acme.prototype.acmeFlow = function (domain, callback) {
}
var that = this;
that.registerUser(email, function (error) {
if (error && error.reason !== AcmeError.ALREADY_EXISTS) return callback(error);
this.registerUser(function (error) {
if (error) return callback(error);
that.registerDomain(domain, function (error, result) {
if (error) return callback(error);
+10 -1
View File
@@ -17,6 +17,7 @@ var acme = require('./cert/acme.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
sysinfo = require('./sysinfo.js'),
user = require('./user.js'),
util = require('util'),
waitForDns = require('./waitfordns.js'),
x509 = require('x509');
@@ -64,7 +65,15 @@ function getApi(callback) {
var options = { };
options.prod = tlsConfig.provider.match(/.*-prod/) !== null;
callback(null, api, options);
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
// we cannot use admin@fqdn because the user might not have set it up.
// we simply update the account with the latest email we have each time when getting letsencrypt certs
// https://github.com/ietf-wg-acme/acme/issues/30
user.getOwner(function (error, owner) {
options.email = error ? 'admin@cloudron.io' : owner.email; // can error if not activated yet
callback(null, api, options);
});
});
}
+18 -2
View File
@@ -13,6 +13,7 @@ exports = module.exports = {
sendHeartbeat: sendHeartbeat,
updateToLatest: updateToLatest,
update: update,
reboot: reboot,
migrate: migrate,
@@ -111,6 +112,7 @@ CloudronError.BAD_EMAIL = 'Bad email';
CloudronError.BAD_PASSWORD = 'Bad password';
CloudronError.BAD_NAME = 'Bad name';
CloudronError.BAD_STATE = 'Bad state';
CloudronError.ALREADY_UPTODATE = 'No Update Available';
CloudronError.NOT_FOUND = 'Not found';
function initialize(callback) {
@@ -245,6 +247,7 @@ function getStatus(callback) {
version: config.version(),
boxVersionsUrl: config.get('boxVersionsUrl'),
apiServerOrigin: config.apiServerOrigin(), // used by CaaS tool
provider: config.provider(),
cloudronName: cloudronName
});
});
@@ -502,6 +505,9 @@ function update(boxUpdateInfo, callback) {
var error = locker.lock(locker.OP_BOX_UPDATE);
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
// ensure tools can 'wait' on progress
progress.set(progress.UPDATE, 0, 'Starting');
// initiate the update/upgrade but do not wait for it
if (boxUpdateInfo.upgrade) {
debug('Starting upgrade');
@@ -524,6 +530,16 @@ function update(boxUpdateInfo, callback) {
callback(null);
}
function updateToLatest(callback) {
assert.strictEqual(typeof callback, 'function');
var boxUpdateInfo = updateChecker.getUpdateInfo().box;
if (!boxUpdateInfo) return callback(new CloudronError(CloudronError.ALREADY_UPTODATE, 'No update available'));
update(boxUpdateInfo, callback);
}
function doUpgrade(boxUpdateInfo, callback) {
assert(boxUpdateInfo !== null && typeof boxUpdateInfo === 'object');
@@ -616,8 +632,8 @@ function backup(callback) {
var error = locker.lock(locker.OP_FULL_BACKUP);
if (error) return callback(new CloudronError(CloudronError.BAD_STATE, error.message));
// clearing backup ensures tools can 'wait' on progress
progress.clear(progress.BACKUP);
// ensure tools can 'wait' on progress
progress.set(progress.BACKUP, 0, 'Starting');
// start the backup operation in the background
backupBoxAndApps(function (error) {
+4 -6
View File
@@ -130,6 +130,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
isAppContainer = !cmd;
var manifest = app.manifest;
var developmentMode = !!manifest.developmentMode;
var exposedPorts = {}, dockerPortBindings = { };
var stdEnv = [
'CLOUDRON=1',
@@ -155,7 +156,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
dockerPortBindings[containerPort + '/tcp'] = [ { HostIp: '0.0.0.0', HostPort: hostPort + '' } ];
}
var memoryLimit = manifest.memoryLimit || 1024 * 1024 * 200; // 200mb by default
var memoryLimit = manifest.memoryLimit || (developmentMode ? 0 : 1024 * 1024 * 200); // 200mb by default
// for subcontainers, this should ideally be false. but docker does not allow network sharing if the app container is not running
// this means cloudron exec does not work
var isolatedNetworkNs = true;
@@ -170,7 +171,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
Hostname: isolatedNetworkNs ? (semver.gte(targetBoxVersion(app.manifest), '0.0.77') ? app.location : config.appFqdn(app.location)) : null,
Tty: isAppContainer,
Image: app.manifest.dockerImage,
Cmd: cmd,
Cmd: (isAppContainer && developmentMode) ? [ '/bin/sleep', 'infinity' ] : cmd,
Env: stdEnv.concat(addonEnv).concat(portEnv),
ExposedPorts: isAppContainer ? exposedPorts : { },
Volumes: { // see also ReadonlyRootfs
@@ -188,7 +189,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
MemorySwap: memoryLimit, // Memory + Swap
PortBindings: isAppContainer ? dockerPortBindings : { },
PublishAllPorts: false,
ReadonlyRootfs: semver.gte(targetBoxVersion(app.manifest), '0.0.66'), // see also Volumes in startContainer
ReadonlyRootfs: !developmentMode, // see also Volumes in startContainer
RestartPolicy: {
"Name": isAppContainer ? "always" : "no",
"MaximumRetryCount": 0
@@ -202,9 +203,6 @@ function createSubcontainer(app, name, cmd, options, callback) {
};
containerOptions = _.extend(containerOptions, options);
// older versions wanted a writable /var/log
if (semver.lte(targetBoxVersion(app.manifest), '0.0.71')) containerOptions.Volumes['/var/log'] = {};
debugApp(app, 'Creating container for %s with options %j', app.manifest.dockerImage, containerOptions);
docker.createContainer(containerOptions, callback);
+2 -2
View File
@@ -4,10 +4,10 @@ Dear Admin,
A new version of the app '<%= app.manifest.title %>' installed at <%= app.fqdn %> is available!
Please update at your convenience at <%= webadminUrl %>.
The app will update automatically tonight. Alternately, update immediately at <%= webadminUrl %>.
Thank you,
Update Manager
your Cloudron
<% } else { %>
+2 -2
View File
@@ -4,7 +4,7 @@ Dear Admin,
A new version of Cloudron <%= fqdn %> is available!
Please update at your convenience at <%= webadminUrl %>.
Your Cloudron will update automatically tonight. Alternately, update immediately at <%= webadminUrl %>.
Changelog:
<% for (var i = 0; i < changelog.length; i++) { %>
@@ -12,7 +12,7 @@ Changelog:
<% } %>
Thank you,
Update Manager
your Cloudron
<% } else { %>
+2 -2
View File
@@ -35,7 +35,7 @@ var assert = require('assert'),
path = require('path'),
safe = require('safetydance'),
smtpTransport = require('nodemailer-smtp-transport'),
userdb = require('./userdb.js'),
users = require('./user.js'),
util = require('util'),
_ = require('underscore');
@@ -184,7 +184,7 @@ function render(templateFile, params) {
}
function getAdminEmails(callback) {
userdb.getAllAdmins(function (error, admins) {
users.getAllAdmins(function (error, admins) {
if (error) return callback(error);
var adminEmails = [ ];
-3
View File
@@ -369,8 +369,5 @@ function exec(req, res, next) {
duplexStream.pipe(res.socket);
res.socket.pipe(duplexStream);
res.on('close', duplexStream.close);
});
}
+3 -6
View File
@@ -23,8 +23,7 @@ var assert = require('assert'),
debug = require('debug')('box:routes/cloudron'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
superagent = require('superagent'),
updateChecker = require('../updatechecker.js');
superagent = require('superagent');
/**
* Creating an admin user and activate the cloudron.
@@ -118,11 +117,9 @@ function getConfig(req, res, next) {
}
function update(req, res, next) {
var boxUpdateInfo = updateChecker.getUpdateInfo().box;
if (!boxUpdateInfo) return next(new HttpError(422, 'No update available'));
// this only initiates the update, progress can be checked via the progress route
cloudron.update(boxUpdateInfo, function (error) {
cloudron.updateToLatest(function (error) {
if (error && error.reason === CloudronError.ALREADY_UPTODATE) return next(new HttpError(422, error.message));
if (error && error.reason === CloudronError.BAD_STATE) return next(new HttpError(409, error.message));
if (error) return next(new HttpError(500, error));
+17 -2
View File
@@ -3,7 +3,8 @@
'use strict';
exports = module.exports = {
backup: backup
backup: backup,
update: update
};
var cloudron = require('../cloudron.js'),
@@ -13,7 +14,7 @@ var cloudron = require('../cloudron.js'),
HttpSuccess = require('connect-lastmile').HttpSuccess;
function backup(req, res, next) {
debug('trigger backup');
debug('triggering backup');
// note that cloudron.backup only waits for backup initiation and not for backup to complete
// backup progress can be checked up ny polling the progress api call
@@ -24,3 +25,17 @@ function backup(req, res, next) {
next(new HttpSuccess(202, {}));
});
}
function update(req, res, next) {
debug('triggering update');
// this only initiates the update, progress can be checked via the progress route
cloudron.updateToLatest(function (error) {
if (error && error.reason === CloudronError.ALREADY_UPTODATE) return next(new HttpError(422, error.message));
if (error && error.reason === CloudronError.BAD_STATE) return next(new HttpError(409, error.message));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(202, {}));
});
}
+2 -12
View File
@@ -16,23 +16,13 @@ exports = module.exports = {
};
var assert = require('assert'),
generatePassword = require('password-generator'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
user = require('../user.js'),
tokendb = require('../tokendb.js'),
UserError = user.UserError;
// http://stackoverflow.com/questions/1497481/javascript-password-generator#1497512
function generatePassword() {
var length = 8,
charset = 'abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
retVal = '';
for (var i = 0, n = charset.length; i < length; ++i) {
retVal += charset.charAt(Math.floor(Math.random() * n));
}
return retVal;
}
function profile(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
@@ -56,7 +46,7 @@ function createUser(req, res, next) {
if (typeof req.body.email !== 'string') return next(new HttpError(400, 'email must be string'));
var username = req.body.username;
var password = generatePassword();
var password = generatePassword(8, true /* memorable */);
var email = req.body.email;
user.create(username, password, email, false /* admin */, req.user /* creator */, function (error, user) {
+1
View File
@@ -221,6 +221,7 @@ function initializeInternalExpressSync() {
// internal routes
router.post('/api/v1/backup', routes.internal.backup);
router.post('/api/v1/update', routes.internal.update);
return httpServer;
}
+63
View File
@@ -106,6 +106,30 @@ describe('User', function () {
});
});
describe('getOwner', function() {
before(cleanupUsers);
after(cleanupUsers);
it('fails because there is no owner', function (done) {
user.getOwner(function (error) {
expect(error.reason).to.be(UserError.NOT_FOUND);
done();
});
});
it('succeeds', function (done) {
createUser(function (error) {
if (error) return done(error);
user.getOwner(function (error, owner) {
expect(error).to.be(null);
expect(owner.email).to.be(EMAIL);
done();
});
});
});
});
describe('verify', function () {
before(createUser);
after(cleanupUsers);
@@ -301,6 +325,45 @@ describe('User', function () {
});
});
describe('get admins', function () {
before(createUser);
after(cleanupUsers);
it('succeeds for one admins', function (done) {
user.getAllAdmins(function (error, admins) {
expect(error).to.eql(null);
expect(admins.length).to.equal(1);
expect(admins[0].username).to.equal(USERNAME);
done();
});
});
it('succeeds for two admins', function (done) {
var user1 = {
username: 'seconduser',
password: 'foobar',
email: 'some@thi.ng'
};
user.create(user1.username, user1.password, user1.email, false, { username: USERNAME, email: EMAIL } /* invitor */, function (error, result) {
expect(error).to.eql(null);
expect(result).to.be.ok();
user.changeAdmin(user1.username, true, function (error) {
expect(error).to.eql(null);
user.getAllAdmins(function (error, admins) {
expect(error).to.eql(null);
expect(admins.length).to.equal(2);
expect(admins[0].username).to.equal(USERNAME);
expect(admins[1].username).to.equal(user1.username);
done();
});
});
});
});
});
describe('password change', function () {
before(createUser);
after(cleanupUsers);
+1 -1
View File
@@ -67,7 +67,7 @@ function getAppUpdates(callback) {
var newManifest = latestAppVersions[apps[i].appStoreId].manifest;
var newVersion = newManifest.version;
if (newVersion !== oldVersion) {
if (semver.gt(newVersion, oldVersion)) {
appUpdateInfo[apps[i].id] = latestAppVersions[apps[i].appStoreId];
debug('Update available for %s (%s) from %s to %s', apps[i].location, apps[i].id, oldVersion, newVersion);
}
+20 -1
View File
@@ -13,11 +13,13 @@ exports = module.exports = {
get: getUser,
getByResetToken: getByResetToken,
changeAdmin: changeAdmin,
getAllAdmins: getAllAdmins,
resetPasswordByIdentifier: resetPasswordByIdentifier,
setPassword: setPassword,
changePassword: changePassword,
update: updateUser,
createOwner: createOwner
createOwner: createOwner,
getOwner: getOwner
};
var assert = require('assert'),
@@ -290,6 +292,15 @@ function changeAdmin(username, admin, callback) {
});
}
function getAllAdmins(callback) {
assert.strictEqual(typeof callback, 'function');
userdb.getAllAdmins(function (error, admins) {
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
callback(null, admins);
});
}
function resetPasswordByIdentifier(identifier, callback) {
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof callback, 'function');
@@ -382,3 +393,11 @@ function createOwner(username, password, email, callback) {
});
}
function getOwner(callback) {
userdb.getOwner(function (error, owner) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND));
if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));
return callback(null, owner);
});
}
+13
View File
@@ -6,6 +6,7 @@ exports = module.exports = {
getByEmail: getByEmail,
getByAccessToken: getByAccessToken,
getByResetToken: getByResetToken,
getOwner: getOwner,
getAll: getAll,
getAllAdmins: getAllAdmins,
add: add,
@@ -56,6 +57,18 @@ function getByEmail(email, callback) {
});
}
function getOwner(callback) {
assert.strictEqual(typeof callback, 'function');
// the first created user it the admin
database.query('SELECT ' + USERS_FIELDS + ' FROM users WHERE admin=1 ORDER BY createdAt LIMIT 1', function (error, result) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (result.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
callback(null, result[0]);
});
}
function getByResetToken(resetToken, callback) {
assert.strictEqual(typeof resetToken, 'string');
assert.strictEqual(typeof callback, 'function');
+2 -2
View File
@@ -1,8 +1,8 @@
/**
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
* @author Alex_Crack
* @version v0.0.5
* @version v0.1.0
* @link https://github.com/alexcrack/angular-ui-notification
* @license MIT
*/
.ui-notification{position:fixed;z-index:9999;top:-100px;right:10px;width:300px;cursor:pointer;-webkit-transition:all ease .5s;-o-transition:all ease .5s;transition:all ease .5s;color:#fff;background:#337ab7;box-shadow:5px 5px 10px rgba(0,0,0,.3)}.ui-notification.killed{-webkit-transition:opacity ease 1s;-o-transition:opacity ease 1s;transition:opacity ease 1s;opacity:0}.ui-notification>h3{font-size:14px;font-weight:700;display:block;margin:10px 10px 0;padding:0 0 5px;text-align:left;border-bottom:1px solid rgba(255,255,255,.3)}.ui-notification a{color:#fff}.ui-notification a:hover{text-decoration:underline}.ui-notification>.message{margin:10px}.ui-notification.warning{color:#fff;background:#f0ad4e}.ui-notification.error{color:#fff;background:#d9534f}.ui-notification.success{color:#fff;background:#5cb85c}.ui-notification.info{color:#fff;background:#5bc0de}.ui-notification:hover{opacity:.7}
.ui-notification{position:fixed;z-index:9999;width:300px;cursor:pointer;-webkit-transition:all ease .5s;-o-transition:all ease .5s;transition:all ease .5s;color:#fff;border-radius:0;background:#337ab7;box-shadow:5px 5px 10px rgba(0,0,0,.3)}.ui-notification.killed{-webkit-transition:opacity ease 1s;-o-transition:opacity ease 1s;transition:opacity ease 1s;opacity:0}.ui-notification>h3{font-size:14px;font-weight:700;display:block;margin:10px 10px 0;padding:0 0 5px;text-align:left;border-bottom:1px solid rgba(255,255,255,.3)}.ui-notification a{color:#fff}.ui-notification a:hover{text-decoration:underline}.ui-notification>.message{margin:10px}.ui-notification.warning{color:#fff;background:#f0ad4e}.ui-notification.error{color:#fff;background:#d9534f}.ui-notification.success{color:#fff;background:#5cb85c}.ui-notification.info{color:#fff;background:#5bc0de}.ui-notification:hover{opacity:.7}
+2 -2
View File
@@ -1,8 +1,8 @@
/**
* angular-ui-notification - Angular.js service providing simple notifications using Bootstrap 3 styles with css transitions for animating
* @author Alex_Crack
* @version v0.0.5
* @version v0.1.0
* @link https://github.com/alexcrack/angular-ui-notification
* @license MIT
*/
angular.module("ui-notification",[]),angular.module("ui-notification").value("uiNotificationTemplates","angular-ui-notification.html"),angular.module("ui-notification").factory("Notification",["$timeout","uiNotificationTemplates","$http","$compile","$templateCache","$rootScope","$injector","$sce",function(t,e,i,n,a,o,l,r){var s=10,c=10,u=10,f=10,m=5e3,p=[],d=function(l,d){"object"!=typeof l&&(l={message:l}),l.template=l.template?l.template:e,l.delay=angular.isUndefined(l.delay)?m:l.delay,l.type=d?d:"",i.get(l.template,{cache:a}).success(function(e){var i=o.$new();if(i.message=r.trustAsHtml(l.message),i.title=r.trustAsHtml(l.title),i.t=l.type.substr(0,1),i.delay=l.delay,"object"==typeof l.scope)for(var a in l.scope)i[a]=l.scope[a];var m=function(){for(var t=0,e=0,i=s,n=p.length-1;n>=0;n--){var a=p[n],o=parseInt(a[0].offsetHeight),l=parseInt(a[0].offsetWidth);r+o>window.innerHeight&&(i=s,e++,t=0);var r=i+(0===t?0:u),m=c+e*(f+l);a.css("top",r+"px"),a.css("right",m+"px"),i=r+o,t++}},d=n(e)(i);d.addClass(l.type),d.bind("webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd click",function(t){t=t.originalEvent||t,("click"===t.type||"opacity"===t.propertyName&&t.elapsedTime>=1)&&(d.remove(),p.splice(p.indexOf(d),1),m())}),angular.isNumber(l.delay)&&t(function(){d.addClass("killed")},l.delay),angular.element(document.getElementsByTagName("body")).append(d),p.push(d),t(m)}).error(function(t){throw new Error("Template ("+l.template+") could not be loaded. "+t)})};return d.config=function(t){s=t.top?t.top:s,u=t.verticalSpacing?t.verticalSpacing:u},d.primary=function(t){this(t,"primary")},d.error=function(t){this(t,"error")},d.success=function(t){this(t,"success")},d.info=function(t){this(t,"info")},d.warning=function(t){this(t,"warning")},d.clearAll=function(){var t=angular.element(document.getElementsByClassName("ui-notification"));t&&angular.forEach(t,function(t){t.remove()})},d}]),angular.module("ui-notification").run(["$templateCache",function(t){t.put("angular-ui-notification.html",'<div class="ui-notification"><h3 ng-show="title" ng-bind-html="title"></h3><div class="message" ng-bind-html="message"></div></div>')}]);
angular.module("ui-notification",[]),angular.module("ui-notification").provider("Notification",function(){this.options={delay:5e3,startTop:10,startRight:10,verticalSpacing:10,horizontalSpacing:10,positionX:"right",positionY:"top",replaceMessage:!1,templateUrl:"angular-ui-notification.html"},this.setOptions=function(t){if(!angular.isObject(t))throw new Error("Options should be an object!");this.options=angular.extend({},this.options,t)},this.$get=["$timeout","$http","$compile","$templateCache","$rootScope","$injector","$sce","$q","$window",function(t,e,i,n,o,s,a,r,l){var p=this.options,c=p.startTop,u=p.startRight,d=p.verticalSpacing,m=p.horizontalSpacing,g=p.delay,f=[],h=!1,y=function(s,y){var v=r.defer();return"object"!=typeof s&&(s={message:s}),s.scope=s.scope?s.scope:o,s.template=s.templateUrl?s.templateUrl:p.templateUrl,s.delay=angular.isUndefined(s.delay)?g:s.delay,s.type=y||p.type||"",s.positionY=s.positionY?s.positionY:p.positionY,s.positionX=s.positionX?s.positionX:p.positionX,s.replaceMessage=s.replaceMessage?s.replaceMessage:p.replaceMessage,e.get(s.template,{cache:n}).success(function(e){var n=s.scope.$new();n.message=a.trustAsHtml(s.message),n.title=a.trustAsHtml(s.title),n.t=s.type.substr(0,1),n.delay=s.delay;var o=function(){for(var t=0,e=0,i=c,n=u,o=[],a=f.length-1;a>=0;a--){var r=f[a];if(s.replaceMessage&&a<f.length-1)r.addClass("killed");else{var l=parseInt(r[0].offsetHeight),p=parseInt(r[0].offsetWidth),g=o[r._positionY+r._positionX];h+l>window.innerHeight&&(g=c,e++,t=0);var h=i=g?0===t?g:g+d:c,y=n+e*(m+p);r.css(r._positionY,h+"px"),"center"==r._positionX?r.css("left",parseInt(window.innerWidth/2-p/2)+"px"):r.css(r._positionX,y+"px"),o[r._positionY+r._positionX]=h+l,t++}}},r=i(e)(n);r._positionY=s.positionY,r._positionX=s.positionX,r.addClass(s.type),r.bind("webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd click",function(t){t=t.originalEvent||t,("click"===t.type||"opacity"===t.propertyName&&t.elapsedTime>=1)&&(r.remove(),f.splice(f.indexOf(r),1),o())}),angular.isNumber(s.delay)&&t(function(){r.addClass("killed")},s.delay),angular.element(document.getElementsByTagName("body")).append(r);var p=-(parseInt(r[0].offsetHeight)+50);r.css(r._positionY,p+"px"),f.push(r),n._templateElement=r,n.kill=function(e){e?(f.splice(f.indexOf(n._templateElement),1),n._templateElement.remove(),t(o)):n._templateElement.addClass("killed")},t(o),h||(angular.element(l).bind("resize",function(){t(o)}),h=!0),v.resolve(n)}).error(function(t){throw new Error("Template ("+s.template+") could not be loaded. "+t)}),v.promise};return y.primary=function(t){return this(t,"primary")},y.error=function(t){return this(t,"error")},y.success=function(t){return this(t,"success")},y.info=function(t){return this(t,"info")},y.warning=function(t){return this(t,"warning")},y.clearAll=function(){angular.forEach(f,function(t){t.addClass("killed")})},y}]}),angular.module("ui-notification").run(["$templateCache",function(t){t.put("angular-ui-notification.html",'<div class="ui-notification"><h3 ng-show="title" ng-bind-html="title"></h3><div class="message" ng-bind-html="message"></div></div>')}]);
+4
View File
@@ -0,0 +1,4 @@
/*! showdown-target-blank 02-11-2015 */
!function(){"use strict";var a=function(){return[{type:"output",regex:"<a(.*?)>",replace:function(a,b){return'<a target="_blank"'+b+">"}}]};"undefined"!=typeof window&&window.showdown&&window.showdown.extensions&&window.showdown.extension("targetblank",a),"undefined"!=typeof module&&(module.exports=a)}();
//# sourceMappingURL=showdown-target-blank.min.js.map
+1
View File
@@ -45,6 +45,7 @@
<!-- Showdown (markdown converter) -->
<script src="3rdparty/js/showdown-1.1.0.min.js"></script>
<script src="3rdparty/js/showdown-target-blank.min.js"></script>
<!-- Main Application -->
<script src="js/index.js"></script>
+22 -1
View File
@@ -99,7 +99,28 @@ angular.module('Application').service('Client', ['$http', 'md5', 'Notification',
message = error;
}
Notification.error({ title: 'Cloudron Error', message: message, delay: 5000 });
Notification.error({ title: 'Cloudron Error', message: message });
};
/*
Example usage with an action:
var actionScope = $scope.$new(true);
actionScope.action = '/#/certs';
Client.notify('title', 'message', true, actionScope);
*/
Client.prototype.notify = function (title, message, delay, actionScope) {
var options = { title: title, message: message, delay: delay};
if (actionScope) {
if (typeof actionScope.action !== 'string') throw('an actionScope has to have an action url');
options.scope = actionScope;
}
Notification.error(options);
};
Client.prototype.setReady = function () {
+19 -3
View File
@@ -1,14 +1,25 @@
'use strict';
/* global angular:false */
/* global showdown:false */
// deal with accessToken in the query, this is passed for example on password reset
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
if (search.accessToken) localStorage.token = search.accessToken;
// create main application module
var app = angular.module('Application', ['ngRoute', 'ngAnimate', 'ngSanitize', 'angular-md5', 'slick', 'ui-notification']);
app.config(['NotificationProvider', function (NotificationProvider) {
NotificationProvider.setOptions({
delay: 10000,
startTop: 60,
positionX: 'left',
templateUrl: 'templates/notification.html'
});
}]);
// setup all major application routes
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
@@ -162,20 +173,25 @@ app.filter('prettyDate', function () {
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31)
return;
return day_diff == 0 && (
return day_diff === 0 && (
diff < 60 && 'just now' ||
diff < 120 && '1 minute ago' ||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
diff < 7200 && '1 hour ago' ||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
day_diff == 1 && 'Yesterday' ||
day_diff === 1 && 'Yesterday' ||
day_diff < 7 && day_diff + ' days ago' ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
};
});
app.filter('markdown2html', function () {
var converter = new showdown.Converter();
var converter = new showdown.Converter({
extensions: ['targetblank'],
simplifiedAutoLink: true,
strikethrough: true,
tables: true
});
return function (text) {
return converter.makeHtml(text);
+13
View File
@@ -105,6 +105,19 @@ angular.module('Application').controller('MainController', ['$scope', '$route',
$scope.config = Client.getConfig();
$scope.initialized = true;
// check if we have aws credentials if selfhosting
if ($scope.config.isCustomDomain) {
Client.getDnsConfig(function (error, result) {
if (error) return console.error(error);
if (result.provider === 'route53' && (!result.accessKeyId || !result.secretAccessKey)) {
var actionScope = $scope.$new(true);
actionScope.action = '/#/certs';
Client.notify('Missing AWS credentials', 'Please provide AWS credentials, click here to add them.', true, actionScope);
}
});
}
});
});
});
+1
View File
@@ -247,6 +247,7 @@ app.controller('FinishController', ['$scope', '$location', 'Wizard', 'Client', f
app.controller('SetupController', ['$scope', '$location', 'Client', 'Wizard', function ($scope, $location, Client, Wizard) {
$scope.initialized = false;
$scope.wizard = Wizard;
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
+2 -2
View File
@@ -38,13 +38,13 @@
<body class="setup" ng-app="Application" ng-controller="SetupController">
<center ng-show="wizard.provider === 'caas' && !setupToken">
<center ng-show="wizard.provider === 'caas' && !wizard.setupToken">
<h1> <i class="fa fa-frown-o fa-fw text-danger"></i> No setup token provided. </h1>
Please use the setup link for this cloudron.
</center>
<div class="main-container">
<div class="row" ng-show="initialized && !busy && !(wizard.provider === 'caas' && !setupToken)">
<div class="row" ng-show="initialized && !busy && !(wizard.provider === 'caas' && !wizard.setupToken)">
<div class="col-md-8 col-md-offset-2">
<div class="card" style="max-width: none; padding: 20px;">
<form role="form" name="setup_form" novalidate>
+7
View File
@@ -0,0 +1,7 @@
<div class="ui-notification">
<h3 ng-show="title" ng-bind-html="title"></h3>
<div class="message">
<a href="{{action}}" ng-show="action" ng-bind-html="message"></a>
<span ng-hide="action" ng-bind-html="message"></span>
</div>
</div>
+29 -10
View File
@@ -158,19 +158,17 @@ html {
.grid-item-bottom {
display: none;
position: absolute;
top: 10px;
padding: 10px 15px;
border-top: 1px solid #ddd;
background-color: white;
right: -10px;
opacity: 0;
background-color: transparent;
transition: all 250ms;
@media(min-width:768px) {
display: block;
position: absolute;
top: 0;
right: -10px;
opacity: 0;
background-color: transparent;
transition: all 250ms;
}
}
@@ -879,4 +877,25 @@ $graphs-success-alt: lighten(#27CE65, 20%);
margin-top: 0;
margin-bottom: 20px;
}
}
}
// ----------------------------
// Notification
// ----------------------------
.ui-notification {
cursor: auto;
& a {
color: white;
&:hover {
color: white;
text-decoration: underline;
}
}
&:hover {
opacity: 1;
}
}
+3 -6
View File
@@ -50,6 +50,7 @@
<br/>
<div class="hide">
<label class="control-label" for="appConfigureCertificateInput" ng-show="config.isCustomDomain">Certificate (optional)</label>
<div class="has-error text-center" ng-show="appConfigure.error.cert && config.isCustomDomain">{{ appConfigure.error.cert }}</div>
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.certificate.$dirty && appConfigure.error.cert }" ng-show="config.isCustomDomain">
@@ -70,6 +71,7 @@
</span>
</div>
</div>
</div>
<a ng-show="!!appConfigure.app.manifest.configurePath" ng-href="https://{{ appConfigure.app.location }}{{ !appConfigure.app.location ? '' : (config.isCustomDomain ? '.' : '-') }}{{ config.fqdn }}/{{ appConfigure.app.manifest.configurePath }}" target="_blank">Application Specific Settings</a>
<br/>
@@ -284,9 +286,6 @@
</div>
</div>
<div class="grid-item-bottom" ng-show="user.admin">
<br/>
<br/>
<div>
<a href="" ng-click="showUninstall(app)" title="Uninstall App"><i class="fa fa-remove scale"></i></a>
</div>
@@ -298,12 +297,10 @@
<div ng-show="(app | installSuccess) == true">
<a href="" ng-click="showConfigure(app)" title="Configure App"><i class="fa fa-wrench scale"></i></a>
</div>
<br/>
</div>
<!-- we check the version here because the box updater does not know when an app gets updated -->
<div class="app-update-badge" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
<div class="app-update-badge" ng-show="config.update.apps[app.id].manifest.version && (app | installSuccess)">
<a href="" ng-click="showUpdate(app)" title="Update Available"><i class="fa fa-asterisk fa-2x text-success scale"></i></a>
</div>
</a>
+2
View File
@@ -44,6 +44,7 @@
<br/>
<div class="hide">
<label class="control-label" for="appInstallCertificateInput" ng-show="config.isCustomDomain">Certificate (optional)</label>
<div class="has-error text-center" ng-show="appInstall.error.cert && config.isCustomDomain">{{ appInstall.error.cert }}</div>
<div class="form-group" ng-class="{ 'has-error': !appInstallForm.certificate.$dirty && appInstall.error.cert }" ng-show="config.isCustomDomain">
@@ -64,6 +65,7 @@
</span>
</div>
</div>
</div>
<input class="ng-hide" type="submit" ng-disabled="appInstallForm.$invalid || busy"/>
</form>
+4 -2
View File
@@ -64,8 +64,10 @@
<div class="col-md-12">
<form name="defaultCertForm" ng-submit="setDefaultCert()">
<fieldset>
<p>By default certificates will be obtained via <a href="https://letsencrypt.org/" target="_blank">Lets Encrypt</a>.</p>
<br/>
<label class="control-label" for="defaultCertInput">Fallback Certificate</label>
<p>A wildcard certificate that will be used for apps installed without a specific certificate.</p>
<p>A wildcard certificate that will be used for apps where getting a Lets Encrypt certificate failed. This might be due to rate limits on Lets Encrypt side.</p>
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">
@@ -91,7 +93,7 @@
</form>
</div>
</div>
<div class="row">
<div class="row hide">
<div class="col-md-12">
<form name="adminCertForm" ng-submit="setAdminCert()">
<fieldset>