Compare commits

...

331 Commits

Author SHA1 Message Date
Girish Ramakrishnan 88d134dc1b add to changes 2025-05-07 10:32:59 +02:00
Johannes Zellner cd3117f9b4 Bring back immich vectors hook in postgres addon
cherry-picked from aa46285b8f
2025-05-07 10:30:31 +02:00
Girish Ramakrishnan f93462d88c 8.3.1 changes 2025-03-18 23:40:01 +01:00
Girish Ramakrishnan e3982e48ea postgres: fix illegal instruction issue
we have to disable -march=native when compiling as per
https://github.com/pgvector/pgvector?tab=readme-ov-file#portability

https://github.com/pgvector/pgvector/issues/143
https://github.com/pgvector/pgvector/issues/752
https://github.com/pgvector/pgvector/issues/389
2025-03-18 23:38:40 +01:00
Girish Ramakrishnan f77296bb2c update changelog for 8.3.0 2025-03-03 09:32:34 +01:00
Johannes Zellner c0f7220040 Fix fs promise usage in sftp addon
(cherry picked from commit da8a7041d1)
2025-03-03 09:30:20 +01:00
Girish Ramakrishnan d435b8b4e3 base image 5.0.0
(cherry picked from commit 3f3bb4d3b7)
2025-02-28 23:11:00 +01:00
Girish Ramakrishnan c478ace8bd skip check when no ipv4/ipv6 2025-02-02 08:34:08 +01:00
Girish Ramakrishnan 5cfec4c371 remove duplicate inwx form fields 2025-02-02 07:30:19 +01:00
Girish Ramakrishnan 40cdf0c94d Update changelog 2025-02-01 09:19:16 +01:00
Girish Ramakrishnan 9908031b68 cloudflare: list recordrecord API does not return zone_id anymore
(cherry picked from commit 6c7341b9f3)
2025-02-01 09:17:12 +01:00
Girish Ramakrishnan c5b48b4386 cloudron-support: auto cleanup
(cherry picked from commit 68066cdd48)
2025-02-01 09:16:59 +01:00
Girish Ramakrishnan 11fd3cafb5 const
(cherry picked from commit d1da77d6bc)
2025-02-01 09:16:44 +01:00
Girish Ramakrishnan 18dda10b54 remove usage of util.format
(cherry picked from commit 6cd97d2cb9)
2025-02-01 09:16:36 +01:00
Girish Ramakrishnan 1a73ddea23 req.connection.remoteAddress is deprecated
(cherry picked from commit 74f4849144)
2025-02-01 09:16:11 +01:00
Girish Ramakrishnan f15b7dd75c username: only ending with .app is reserved
(cherry picked from commit b43fa38350)
2025-02-01 09:15:44 +01:00
Girish Ramakrishnan 51ed5b78f2 backups: add preserve attributes checkbox
(cherry picked from commit 837d5803c8)
2025-02-01 09:14:34 +01:00
Girish Ramakrishnan fb2ec52464 update mail image
(cherry picked from commit 84a1f40115)
2025-02-01 09:13:42 +01:00
Girish Ramakrishnan d158ba0464 mail: rebuild index
(cherry picked from commit abb40b3ad7)
2025-02-01 09:12:51 +01:00
Girish Ramakrishnan b6b1eb2353 mail: disable ocr in tika
(cherry picked from commit dfd54b7b54)
2025-02-01 09:12:07 +01:00
Johannes Zellner fd8eed048a Add inwx to dns setup
(cherry picked from commit 2ac3c1fe6e)
2025-02-01 09:11:13 +01:00
Girish Ramakrishnan 25ee2170f6 restore: fix crash with invalid backup id
(cherry picked from commit 230599417e)
2025-02-01 09:10:48 +01:00
Girish Ramakrishnan c6641d23cd grammar
donald says so

(cherry picked from commit aaab06f21d)
2025-02-01 09:10:08 +01:00
Girish Ramakrishnan f5f6b69d5d mail: add ipv6 rdns check
(cherry picked from commit 6fcfa6cac0)
2025-02-01 09:09:58 +01:00
Girish Ramakrishnan e536c94028 firewall: add dockerproxy 2025-01-03 21:14:19 +01:00
Girish Ramakrishnan d57020d269 firewall: allow udp responses to come back from docker 2025-01-03 19:50:42 +01:00
Girish Ramakrishnan d47aa816d3 firewall: accept ldap connections 2025-01-03 19:33:51 +01:00
Girish Ramakrishnan 29a9b3d68a firewall: use a chain instead of adding rules directly
this helps in updating rules across upgrades
2025-01-03 17:59:24 +01:00
Girish Ramakrishnan b6f70e4bc0 rsync: increase empty dir limit
a mail backup of a mailbox with many folders can have many empty dirs

https://forum.cloudron.io/topic/13047/since-update-to-v8-2-1-backups-fail-with-too-many-empty-directories
2025-01-03 13:01:10 +01:00
Girish Ramakrishnan 73e1e6881e docker: fix parsing of imageRef if no namespace 2025-01-03 10:10:06 +01:00
Girish Ramakrishnan ebc3dfc3f0 mail: update the dns-list plugin 2025-01-03 09:36:11 +01:00
Girish Ramakrishnan 2ae05baec3 add to changelog 2025-01-02 23:53:00 +01:00
Girish Ramakrishnan 746bcb1dd0 firewall: ip6tables requires ipv6 2025-01-02 23:48:19 +01:00
Girish Ramakrishnan 874f8328b8 firewall: wait-interval is deprecated 2025-01-02 23:44:50 +01:00
Girish Ramakrishnan 62e2283992 firewall: add masquerade rule for access via public IP 2025-01-02 23:34:46 +01:00
Girish Ramakrishnan 0cf407b6f5 give mail container a static IP 2025-01-02 23:33:21 +01:00
Girish Ramakrishnan 8a97b7efa4 notifications: send unacked ones first 2025-01-02 16:50:31 +01:00
Girish Ramakrishnan 1e2ca7b835 volumes: test host path validation 2025-01-02 11:46:11 +01:00
Girish Ramakrishnan f7ea847336 do not modify hostPath variable 2025-01-02 11:22:09 +01:00
Girish Ramakrishnan 9d890e1c21 security: fix issue where '/' symlink allows admins to get ssh access
* create a volume
* create symlink to /
* now, create another volume with that symlink as host directory
2025-01-02 11:18:39 +01:00
Girish Ramakrishnan 9c7e9e25ca scheduler: respect cloudron timezone setting 2025-01-02 10:11:14 +01:00
Girish Ramakrishnan 4ffe736d46 mail: dns list crash fix 2025-01-02 09:24:51 +01:00
Girish Ramakrishnan 13d82e5a4d mail: fix issue with dkim signing 2025-01-01 18:33:04 +01:00
Girish Ramakrishnan a7f083dbd1 gandi: get token type in setup view 2025-01-01 15:43:46 +01:00
Girish Ramakrishnan d3b82d68e7 add todo for ipv6 ptr 2024-12-22 12:39:33 +01:00
Girish Ramakrishnan bd961025f6 platform: get shell output as utf8 2024-12-19 16:59:28 +01:00
Girish Ramakrishnan c31da4eb2a add to changelog 2024-12-19 15:40:58 +01:00
Girish Ramakrishnan 812ecf4041 disable archiving for pre-8.2 backups
the sso situation complicates implementing restore for those
2024-12-19 15:31:07 +01:00
Girish Ramakrishnan cd8be9ffb5 archive: appConfig is null for pre-8.2 backups
use backups.manifest when possible instead
2024-12-19 15:21:33 +01:00
Girish Ramakrishnan 40abb446d4 archive: disable button when busy 2024-12-19 15:13:20 +01:00
Johannes Zellner 96d740fb15 Use VITE_CACHE_ID also in index.js 2024-12-19 14:01:54 +01:00
Girish Ramakrishnan 5898436638 test: fix dockerproxy 2024-12-19 13:10:14 +01:00
Girish Ramakrishnan 17fee93002 apps: hide update indicator for normal users 2024-12-19 12:36:47 +01:00
Girish Ramakrishnan 68431ae357 rename functions to avoid mistakes
the remove fields are not clear enough. we sent notes by mistake to
normal users. changing the name and passing role as the argument
will avoid these errors
2024-12-19 12:24:08 +01:00
Girish Ramakrishnan ba6ba44955 use enum for access levels 2024-12-19 12:24:08 +01:00
Girish Ramakrishnan 3b101a2086 remove spurious comment 2024-12-19 12:24:08 +01:00
Johannes Zellner 876fd218af Fix sso ordering in apps listing 2024-12-19 12:22:41 +01:00
Girish Ramakrishnan cbd32e7372 apps: non-admins cannot see notes, checklist and enableBackup 2024-12-19 11:35:20 +01:00
Girish Ramakrishnan 324b82187b readme: reword some things 2024-12-19 10:32:30 +01:00
Girish Ramakrishnan 8d19c351e7 cloudron-support: add link to docs 2024-12-18 10:52:51 +01:00
Girish Ramakrishnan 5c00fb361a cloudron-support: suggest removing nodejs apt 2024-12-18 10:17:05 +01:00
Girish Ramakrishnan 903e0bc568 solr: show state correctly 2024-12-18 07:21:19 +01:00
Girish Ramakrishnan d12a23b73f fts: enable and not enabled 2024-12-18 07:07:07 +01:00
Girish Ramakrishnan 6e34f84b14 Update fts translations 2024-12-17 21:19:29 +01:00
Girish Ramakrishnan c74fa04b7f better text 2024-12-17 19:23:06 +01:00
Girish Ramakrishnan 758b05393c catch app backup error to release lock 2024-12-17 19:08:43 +01:00
Girish Ramakrishnan 219066d8d7 mail_templates: no format 2024-12-17 17:07:35 +01:00
Girish Ramakrishnan 449dd4730f archive: return the id in archive route 2024-12-17 14:33:36 +01:00
Girish Ramakrishnan 73ffe9ce41 link to App Archive 2024-12-17 11:34:53 +01:00
Girish Ramakrishnan c21c24f088 Update translations 2024-12-17 11:01:12 +01:00
Girish Ramakrishnan f35f548ecd mail: fix various upstream plugin changes 2024-12-16 23:57:56 +01:00
Girish Ramakrishnan 69d5283caf mail: use a lock to protect container recreation
needs a lock because the cert code also restart mail server from tasks
2024-12-16 22:34:52 +01:00
Girish Ramakrishnan 43950fc398 ldap: fix crash. function was renamed 2024-12-16 20:29:28 +01:00
Girish Ramakrishnan d2e3b80517 taskworker: add debug 2024-12-16 15:17:35 +01:00
Girish Ramakrishnan 3728d8ecc1 porkbun: incorrect usage of promises 2024-12-16 14:07:03 +01:00
Girish Ramakrishnan dcca524726 porkbun: timeout for all requests flat out 2024-12-16 10:03:16 +01:00
Girish Ramakrishnan 9ec5fc29aa dns: return same type 2024-12-16 09:55:54 +01:00
Girish Ramakrishnan 1d0f3a08f4 porkbun: it is really slow 2024-12-16 09:46:38 +01:00
Girish Ramakrishnan 3d8ffcd0f7 another typo 2024-12-14 23:28:00 +01:00
Girish Ramakrishnan 8c28871b76 typo 2024-12-14 23:25:14 +01:00
Girish Ramakrishnan df53f827c5 release: happy eyeballs workaround 2024-12-14 22:00:45 +01:00
Girish Ramakrishnan 83adcd73a9 sqlite3: images.base is gone 2024-12-14 21:40:47 +01:00
Girish Ramakrishnan 8e6890b4d6 docker: rework image pruning
with our new retagging approach, the Digest ID remains <null> because
this is only set by docker if truly fetched from the registry.

this means that redis container always gets removed...
2024-12-14 20:47:35 +01:00
Girish Ramakrishnan bd107e849b infra: no more images.base 2024-12-14 20:18:07 +01:00
Girish Ramakrishnan 5893f53b43 typo 2024-12-14 19:05:32 +01:00
Girish Ramakrishnan 1894ed7721 box: no oidc messages 2024-12-14 19:04:59 +01:00
Girish Ramakrishnan 96b715de8e apptask: try install via ipv4
our ci app images are not pushed to quay and the tests fail on
ipv6 servers
2024-12-14 18:55:55 +01:00
Girish Ramakrishnan b26890f5b3 release: print the sourceEnv.url 2024-12-14 17:14:41 +01:00
Girish Ramakrishnan 5ae29eabaa docker: try ipv4 and then ipv6 explicitly
To get the ratelimits:
TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest

docker appears to have some simple approach to track ipv6 limits.
2024-12-14 17:05:13 +01:00
Girish Ramakrishnan d9e4aeb518 docker: replace pull with tag to not hit rate limits 2024-12-14 16:16:33 +01:00
Girish Ramakrishnan 6b7edbd552 docker: fallback to quay if docker hub does not work 2024-12-14 15:22:12 +01:00
Girish Ramakrishnan 12f19299a8 docker: only send credentials when registry matches 2024-12-14 14:21:15 +01:00
Girish Ramakrishnan 0008e5a83b docker: parse registry also 2024-12-14 14:10:29 +01:00
Girish Ramakrishnan 0bd1aac0ef refactor 2024-12-14 14:05:53 +01:00
Girish Ramakrishnan 5145344987 docker: do not use auth for cloudron namespace 2024-12-14 14:04:40 +01:00
Girish Ramakrishnan cc980fbc0c add note on manifest id 2024-12-14 14:02:49 +01:00
Girish Ramakrishnan 878caff378 improve the comment 2024-12-14 14:01:38 +01:00
Girish Ramakrishnan 5ce82d6794 docker: parseImageRef 2024-12-14 14:00:05 +01:00
Girish Ramakrishnan d456f91921 tasks: fix active status 2024-12-12 19:09:55 +01:00
Girish Ramakrishnan 3be77fc634 fix link 2024-12-12 15:21:05 +01:00
Girish Ramakrishnan a4e68733ed use ttyUSB0 as placeholder 2024-12-12 12:33:54 +01:00
Johannes Zellner eaae3f824b Also remove postinstall confirm checkbox from app view 2024-12-12 12:20:34 +01:00
Johannes Zellner 8d3b9685a1 Update postgres addon version 2024-12-12 12:01:57 +01:00
Johannes Zellner 3fa354a815 Update translations 2024-12-12 11:53:24 +01:00
Johannes Zellner 512722695e Fix archives for mobile 2024-12-12 11:52:59 +01:00
Johannes Zellner 9ed424a5d9 Add some changes 2024-12-12 11:22:15 +01:00
Johannes Zellner a36ef67305 Update frontened dependencies 2024-12-12 11:19:38 +01:00
Girish Ramakrishnan be340580d4 various notification fixes 2024-12-11 23:58:17 +01:00
Girish Ramakrishnan fbe207dac3 typo 2024-12-11 23:01:00 +01:00
Girish Ramakrishnan f59837f7c3 spurious console 2024-12-11 22:44:04 +01:00
Girish Ramakrishnan d0d0913c70 notifications: add context field 2024-12-11 22:29:00 +01:00
Girish Ramakrishnan 701c25d07a notifications: add back app down and app oom mails 2024-12-11 20:56:15 +01:00
Johannes Zellner d38b4d7b74 Fix notification setting button size and margin 2024-12-11 19:29:05 +01:00
Girish Ramakrishnan 8fd9324048 Fix crash 2024-12-11 19:17:44 +01:00
Girish Ramakrishnan 6004cd17bf notifications: per user email prefs 2024-12-11 19:12:20 +01:00
Girish Ramakrishnan 746e694d7e notifications: rename alert to pin and unpin 2024-12-11 17:31:32 +01:00
Girish Ramakrishnan ead419003b notifications: rename ALERT_ to TYPE_ 2024-12-11 15:29:20 +01:00
Girish Ramakrishnan 6141db8f34 Update ubuntu check to bionic 2024-12-11 15:03:59 +01:00
Girish Ramakrishnan 6993cbeb9f archive: download the config 2024-12-11 10:54:51 +01:00
Girish Ramakrishnan 96f2c6e2aa archive: add button to download the config 2024-12-11 09:50:54 +01:00
Girish Ramakrishnan 65f507bc75 clone: read custom icon from downloaded backup
the backups table does not have icon to save space. only the
archives table has it for the moment.
2024-12-10 22:47:21 +01:00
Girish Ramakrishnan 05d6484d27 clone: label should be from dolly 2024-12-10 21:07:59 +01:00
Girish Ramakrishnan 41bc08a07e backup: move appConfig to backups table
this is useful for clone also to copy notes, operators, checklist
of the time when the backup was made (as opposed to current)

at this point, it's not clear why we need a archives table. it's
an optimization to not have to store icon for every backup.
2024-12-10 21:04:37 +01:00
Girish Ramakrishnan 98058f600e archive: prefill secondary domain correctly 2024-12-10 19:27:19 +01:00
Girish Ramakrishnan 41b302b0b9 apps: unarchive can call add() on it's own
all this because the sso flag is not allowed with optionalSso :/
2024-12-10 19:09:29 +01:00
Girish Ramakrishnan fbe334e7d7 install/unarchive: add support for various fields 2024-12-10 18:39:16 +01:00
Girish Ramakrishnan 9a155491cb move unarchive to apps model 2024-12-10 17:19:12 +01:00
Girish Ramakrishnan ab8ec07f2f clone/unarchive: handle notes and checklist 2024-12-10 17:16:06 +01:00
Girish Ramakrishnan 3e1c886b17 clone: copy devices 2024-12-10 16:49:25 +01:00
Girish Ramakrishnan 21c3d16db5 archive: proxy app cannot be archived 2024-12-10 16:49:25 +01:00
Girish Ramakrishnan 0e181cdc82 archive: implement unarchive
made a separate route instead of reusing install route. this was
because we want to copy over all the old app config as much as
possible.
2024-12-10 16:49:19 +01:00
Girish Ramakrishnan e168be6d97 appstore: remove traces of custom cert 2024-12-10 14:49:54 +01:00
Girish Ramakrishnan f65be99017 appstore: remove ununsed cert input 2024-12-10 14:47:00 +01:00
Girish Ramakrishnan e201d4c896 archive: add confirm delete dialog 2024-12-10 14:26:07 +01:00
Johannes Zellner a8035d01c6 Fix archive app icons 2024-12-10 13:25:44 +01:00
Johannes Zellner 054275f143 appstore id tooltip should be on the string not the table cell 2024-12-10 13:15:09 +01:00
Johannes Zellner e652456d54 vertically align action buttons in archive table 2024-12-10 13:04:57 +01:00
Johannes Zellner 1e6a7d72ab Attach tooltip to body to not break table layout 2024-12-10 13:02:31 +01:00
Johannes Zellner 965054a707 Fix translation typo 2024-12-10 12:58:22 +01:00
Johannes Zellner 9a26dc090e Allow to set DASHBOARD_DEVELOPMENT_ORIGIN in env for local development 2024-12-10 12:56:09 +01:00
Girish Ramakrishnan 30b0d4cced archives: add listing 2024-12-10 12:30:10 +01:00
Girish Ramakrishnan f973536f7f archives: add eventlog 2024-12-10 11:10:35 +01:00
Girish Ramakrishnan 490840b71d archives: use separate table
Cleaner to separate things from the backups table.

* icon, appConfig, appStoreIcon etc are only valid for archives
* older version cloudron does not have appConfig in backups table (so it
  cannot be an archive entry)
2024-12-10 10:36:44 +01:00
Girish Ramakrishnan 2ad93c114e archive: add appConfig, icon and appStoreIcon 2024-12-09 23:25:31 +01:00
Girish Ramakrishnan cec2106cfe update the schema file 2024-12-09 22:42:22 +01:00
Girish Ramakrishnan 9200e6fc63 add archives api 2024-12-09 22:39:28 +01:00
Girish Ramakrishnan 5907975c02 remove App from start/stop/restart 2024-12-09 21:26:35 +01:00
Girish Ramakrishnan fe68887cdd archive: add confirm dialog 2024-12-09 21:22:06 +01:00
Girish Ramakrishnan 24df6edbf1 update archive translations 2024-12-09 19:14:33 +01:00
Girish Ramakrishnan 710bd270d7 apps: add archive action 2024-12-09 18:51:49 +01:00
Girish Ramakrishnan 147e014205 backup: add archive flag 2024-12-09 16:25:31 +01:00
Girish Ramakrishnan 65a7f5f1c6 Use subarray instead of slice
says it's deprecated
2024-12-09 16:14:49 +01:00
Girish Ramakrishnan cfc3a4217d platform: mark apps early
this gives some UI feedback when the platform is starting
2024-12-09 15:04:14 +01:00
Girish Ramakrishnan 35be854997 apptaskmanager: do not schedule tasks until infra ready 2024-12-09 14:46:03 +01:00
Johannes Zellner 58af890abe Do not crash on assert if backup task failed 2024-12-09 13:09:51 +01:00
Girish Ramakrishnan ada878c939 hetzner: add helsinki object storage location 2024-12-09 09:44:35 +01:00
Girish Ramakrishnan 08435fbe26 release: more debugs 2024-12-09 09:06:38 +01:00
Girish Ramakrishnan 00a643e70a release: add the env.tag to output 2024-12-09 09:03:36 +01:00
Girish Ramakrishnan cc759a8427 Add waiting for lock message 2024-12-09 08:40:54 +01:00
Girish Ramakrishnan bb392207ea remove global lock
Currently, the update/apptask/fullbackup/platformstart take a
global lock and cannot run in parallel. This causes situations
where when a user tries to trigger an apptask, it says "waiting for
backup to finish..." etc

The solution is to let them run in parallel. We need a lock at the
app level as app operations running in parallel would be bad (tm).
In addition, the update task needs a lock just for the update part.
We also need multi-process locks. Running tasks as processes is core
to our "kill" strategy.

Various inter process locks were explored:

* node's IPC mechanism with process.send(). But this only works for direct node.js
children. taskworker is run via sudo and the IPC does not work.

* File lock using O_EXCL. Basic ideas to create lock files. While file creation
can be done atomically, it becomes complicated to clean up lock files when
the tasks crash. We need a way to know what locks were held by the crashing task.
flock and friends are not built-into node.js

* sqlite/redis were options but introduce additional deps

* Settled on MySQL based locking. Initial plan was to have row locks
or table locks. Each row is a kind of lock. While implementing, it was found that
we need many types of locks (and not just update lock and app locks). For example,
we need locks for each task type, so that only one task type is active at a time.

* Instead of rows, we can just lock table and have a json blob in it. This hit a road
block that LOCK TABLE is per session and our db layer cannot handle this easily! i.e
when issing two db.query() it might use two different connections from the pool. We have to
expose the connection, release connection etc.

* Next idea was atomic blob update of the blob checking if old blob was same. This approach,
was finally refined into a version field.

Phew!
2024-12-07 20:41:22 +01:00
Girish Ramakrishnan a5b9ff0c3a add to changelog 2024-12-07 11:27:52 +01:00
Johannes Zellner 146afce934 Improve devices error handling 2024-12-06 13:35:52 +01:00
Girish Ramakrishnan de0909248d start.sh: collapse the mkdir lines 2024-12-05 15:53:03 +01:00
Johannes Zellner d5b3a56129 dashboard: show devices error within the form 2024-12-05 15:27:10 +01:00
Johannes Zellner fbed850acc Also validate devices in the setter route 2024-12-05 15:16:06 +01:00
Johannes Zellner 25fb467c02 dashboard: initial UI to attach devices to apps 2024-12-05 14:49:36 +01:00
Johannes Zellner 8493022f75 Allow apps to specify custom devices 2024-12-05 14:21:07 +01:00
Johannes Zellner 621c1ed95a dashboard: import momentjs with all locales 2024-12-05 12:19:58 +01:00
Johannes Zellner 4992e284fb dashboard: never hide or wrap action buttons in app list 2024-12-04 18:15:34 +01:00
Girish Ramakrishnan e4fb040ddf make tests great again 2024-12-04 16:36:05 +01:00
Girish Ramakrishnan 2bfa49cc2e applinks: add tests 2024-12-04 16:17:07 +01:00
Girish Ramakrishnan 3b9d617e37 groups: add events to eventlog 2024-12-04 11:30:30 +01:00
Girish Ramakrishnan fdf8025a02 style: remove -> del 2024-12-03 17:36:50 +01:00
Girish Ramakrishnan 423dfb6ace schema: update comment 2024-12-03 16:33:59 +01:00
Girish Ramakrishnan 0a4aede3a8 eventlog: branding events 2024-12-02 12:18:09 +01:00
Girish Ramakrishnan 872705d58d oidc: use the cloudron name as provider name 2024-12-02 12:01:19 +01:00
Girish Ramakrishnan ca5776e6f3 services: fix oidc usage 2024-12-02 11:00:12 +01:00
Girish Ramakrishnan d4998b5d55 rename view user-settings to user-directory 2024-12-02 09:02:58 +01:00
Girish Ramakrishnan e93f5e3e87 oidc: show name in delete dialog 2024-12-02 08:56:03 +01:00
Girish Ramakrishnan d29bb90c5a update various oidc translations 2024-12-02 08:56:03 +01:00
Girish Ramakrishnan 1230e5c9e7 oidc: add load pattern 2024-12-02 08:36:03 +01:00
Girish Ramakrishnan dc3d23c27b oidc: flatten the export list 2024-12-02 08:31:35 +01:00
Girish Ramakrishnan 6623061c2c services: fix ticks 2024-11-30 18:02:29 +01:00
Girish Ramakrishnan 1ecb853309 mail: attachment search 2024-11-30 17:42:26 +01:00
Girish Ramakrishnan 2a6c52800b system: filesystems in exclude are excluded from content analysis
some disks can be very slow and noisy (at home). this allows users to simply skip them.
also, applicable for large storage boxes
2024-11-30 13:08:21 +01:00
Girish Ramakrishnan 320ddfda2e compute docker df only once 2024-11-30 12:44:15 +01:00
Girish Ramakrishnan 40febc8ef2 system: rename DISK_TYPES to FS_TYPES 2024-11-30 12:04:07 +01:00
Girish Ramakrishnan 56f6519b3e rename disks to filesystems 2024-11-30 12:04:04 +01:00
Girish Ramakrishnan f219abf082 system: indent cache file 2024-11-30 11:11:54 +01:00
Girish Ramakrishnan 742a04d149 system: expose getDisks only for tests 2024-11-30 10:42:06 +01:00
Girish Ramakrishnan 26caacc12e Fix debugs 2024-11-30 10:18:48 +01:00
Girish Ramakrishnan 1497518867 better assert message 2024-11-30 10:18:40 +01:00
Johannes Zellner 1a4a69f365 update postgres to add pgvector extension 2024-11-28 17:37:22 +01:00
Girish Ramakrishnan 78520e09c3 domains: add inwx provider 2024-11-26 19:13:33 +05:30
Girish Ramakrishnan f0207ff161 test: comment it out, it is not run anyway 2024-11-26 15:54:55 +05:30
Girish Ramakrishnan dd45f1c032 cloudron-support: set connect timeout and redirect ping output 2024-11-26 11:15:27 +05:30
Girish Ramakrishnan ddf1c8e385 cloudron-support: clarify ipv6 in kernel 2024-11-26 11:10:58 +05:30
Girish Ramakrishnan 948efbaa76 docker: upgrade docker to 27.3.1 2024-11-23 20:31:44 +05:30
Girish Ramakrishnan ccd1a4319d lint 2024-11-21 19:18:26 +05:30
Girish Ramakrishnan 22be1f1b72 sqlite: create dumps based on the basename 2024-11-21 12:34:06 +05:30
Girish Ramakrishnan 7095862601 sqlite: add some comments 2024-11-21 12:24:27 +05:30
Girish Ramakrishnan fa98e0570f sqlite: change path to paths 2024-11-21 10:02:26 +05:30
Girish Ramakrishnan 4316d3eade add sqlite3 addon take 2
- there is no container id during the addon lifecycle
- sqlite3 requires the localstorage addon to be inited. so this has to
  become like the ftp option
- remove all that child_process streaming stuff. too complicated
2024-11-21 00:13:17 +05:30
Girish Ramakrishnan f8cd0b5f52 add sqlite3 addon 2024-11-21 00:13:17 +05:30
Girish Ramakrishnan a8b3f69acc Update manifestformat 2024-11-21 00:13:17 +05:30
Johannes Zellner 78cb36ea0e Start using POST /api/v1/apps to install 2024-11-20 16:18:37 +01:00
Girish Ramakrishnan b4d58f0609 aws: add a 20min timeout
in some services like b2, the multi-part copy just hangs. this allows
us to retry
2024-11-20 07:13:43 +05:30
Girish Ramakrishnan 18abc214a6 mail: update haraka to 3.0.5 2024-11-20 06:32:13 +05:30
Girish Ramakrishnan 5e3857fd3d Fix assert
NETWORK_ERROR is usually an AggregateError which causes an
assert in BoxError
2024-11-19 17:08:55 +05:30
Johannes Zellner e35b36643c Add more oidc debugs 2024-11-18 18:09:01 +01:00
Johannes Zellner 16fa339025 Add refresh_token grant type 2024-11-18 18:07:32 +01:00
Girish Ramakrishnan 051b0e0fd3 oidc: set a refresh token ttl to avoid warning
oidc-provider NOTICE: default ttl.RefreshToken function called, you SHOULD change it in order to define the expiration for RefreshToken artifacts.
2024-11-18 15:29:14 +05:30
Girish Ramakrishnan 62d3212f88 applink: add timeout when detecting metadata 2024-11-18 08:18:39 +05:30
Girish Ramakrishnan fd96665e97 rsync: show better error message with too many empty dirs, symlinks or executables 2024-11-18 08:11:14 +05:30
Girish Ramakrishnan 8f6637773b shell: add option for maxLines 2024-11-18 07:59:05 +05:30
Girish Ramakrishnan d7f829b3e1 Fix link 2024-11-10 09:35:42 +01:00
Johannes Zellner 3fdb43762b Do not make app dockerImage overflow 2024-11-08 21:39:44 +01:00
Girish Ramakrishnan 7ae02a62fe quote the filename 2024-11-08 21:11:23 +01:00
Johannes Zellner 11cb33fe25 Update dashboard dependencies 2024-11-08 18:33:44 +01:00
Johannes Zellner a09202d1fa Show some error in filemanager if pasting fails 2024-11-08 18:28:57 +01:00
Johannes Zellner fcccccaaae Ask for app restart confirmation 2024-11-08 18:15:34 +01:00
Johannes Zellner 9f80578bab Avoid preview flickering for psd image files 2024-11-08 18:15:34 +01:00
Girish Ramakrishnan 32e3665b7a more changes 2024-11-08 17:15:40 +01:00
Girish Ramakrishnan e9c10b306c update translations 2024-11-08 17:15:40 +01:00
Johannes Zellner dabadcc00e Ensure minimum flexitem width for disk usage 2024-11-08 16:48:40 +01:00
Girish Ramakrishnan 9cc594d633 hetzner: add nbg1 2024-11-08 16:21:25 +01:00
Girish Ramakrishnan 8350eeb751 cloudron-support: rename enable-remote-access to enable-remote-support 2024-11-08 16:01:30 +01:00
Johannes Zellner 7b61bafab7 Fix oidc login layout for long instance names 2024-11-06 17:15:03 +01:00
Girish Ramakrishnan 6407d795ed du: better error handling of du
du can fail when files and directories go missing. luckily, when du fails,
it still provides the best effort output
2024-11-06 14:54:52 +01:00
Girish Ramakrishnan 9cf235af39 boxerror: assign extra fields in all cases 2024-11-06 13:40:37 +01:00
Johannes Zellner 18e5365104 Fix typo 2024-11-05 14:24:40 +01:00
Johannes Zellner c03eff8da2 shell.js using argument array list now 2024-11-05 13:09:27 +01:00
Johannes Zellner 28f79cd6c9 return early if docker ps returns nothing 2024-11-05 13:05:12 +01:00
Girish Ramakrishnan fc2786b07f taskworker: fix programming error 2024-11-01 16:15:32 +01:00
Johannes Zellner 620ad13427 Add more changes 2024-11-01 16:03:19 +01:00
Johannes Zellner 0776442a5f Silence deprecation warning caused by old bootstrap import 2024-10-31 10:29:37 +01:00
Girish Ramakrishnan 4a207395ca middleground in timeout
DO BLR droplets still fail with 1s timeout!
2024-10-31 10:22:55 +01:00
Girish Ramakrishnan 2df983a1cf lower timeout 2024-10-31 09:50:20 +01:00
Girish Ramakrishnan 03e17aea22 taskworker: refactor 2024-10-31 09:46:36 +01:00
Girish Ramakrishnan aefa481c43 network: fix premature connection closures with node 20 and above
the happy eyeballs implementation in node is buggy. ipv4 and ipv6 connections
are made in parallel and whichever responds first is chosen. when there is no
ipv6 (immediately errors with ENETUNREACH/EHOSTUNREACH) and when ipv4 is > 250ms,
the code erroneously times out.

see also https://github.com/nodejs/node/issues/54359

reproduction for those servers:

const options = {
  hostname: 'www.cloudron.io', port: 80, path: '/', method: 'HEAD',
  // family: 4, // uncomment to make it work
};

const req = require('http').request(options, (res) => {
  console.log('statusCode:', res.statusCode);
  res.on('data', () => {}); // drain
});

req.on('socket', (socket) => console.log('Socket assigned to request', socket););
req.on('error', (e) => console.error(e));
req.end();
2024-10-31 09:38:40 +01:00
Girish Ramakrishnan 553c256d31 better debugs 2024-10-30 20:58:37 +01:00
Johannes Zellner b6023afb29 Silence most dashboard sass deprecation warnings 2024-10-30 19:29:24 +01:00
Girish Ramakrishnan 0df1e3a47f appstore: networkError is an aggreate error 2024-10-30 18:30:53 +01:00
Girish Ramakrishnan 78a08c5a0b Use a real string as second argument since message can be undefined 2024-10-30 17:59:55 +01:00
Girish Ramakrishnan 55a880c9ac Fix typo
14a18a42b7
2024-10-30 17:41:57 +01:00
Girish Ramakrishnan 61341b8380 boxerror: always pass second error string 2024-10-30 17:32:12 +01:00
Girish Ramakrishnan a32b567eb1 boxerror: remove unused override 2024-10-30 15:43:53 +01:00
Johannes Zellner 25462d3290 pankow support dropdown buttons so use that in filemanager 2024-10-30 13:05:24 +01:00
Johannes Zellner a9207b392b Folder creation is a query arg not body param 2024-10-30 13:01:26 +01:00
Johannes Zellner c0f3c3bd2b dashboard: update dependencies 2024-10-30 12:28:07 +01:00
Johannes Zellner 8621fbda79 Enable refresh tokens for oidc provider 2024-10-29 16:20:53 +01:00
Johannes Zellner 84de986efd Network mounts should only depend on systemd network-online.target now 2024-10-29 14:07:03 +01:00
Girish Ramakrishnan 0f3ab11532 Update node to 20.18.0
We need https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--network-family-autoselection-attempt-timeout

The happy eyeballs implementation in node is buggy - https://github.com/nodejs/node/issues/54359
2024-10-28 09:55:54 +01:00
Johannes Zellner 6b4a81e471 dashboard: bring back cache busting for old script included assets 2024-10-27 12:10:00 +01:00
Johannes Zellner 14a18a42b7 Avoid crash in externalldap if search result has no username 2024-10-22 14:49:14 +02:00
Johannes Zellner 2c28eddc2b Fix linter errors 2024-10-22 14:40:53 +02:00
Girish Ramakrishnan 1b22ea661c avatar: deliver .png images
this is required for mastodon atleast. if the oidc avatar url, returns
an svg, it crashes!

the profile pic png was created using inkspace:
inkscape -w 96 -h 96 avatar-default-symbolic.svg -o avatar-default-symbolic.png
2024-10-18 22:39:18 +02:00
Girish Ramakrishnan efc3c7532e Move requires to the top 2024-10-18 21:50:38 +02:00
Johannes Zellner a3a807f22c Ensure we stick to dark background in dark mode 2024-10-18 18:33:38 +02:00
Johannes Zellner fac5d3c07b Add code for cloudron-support to check and fix docker version 2024-10-17 12:41:33 +02:00
Girish Ramakrishnan df5ba25010 shell: add explicit bash() function 2024-10-16 10:40:17 +02:00
Johannes Zellner d66db8ca40 Use the correct new redis image 2024-10-15 22:28:52 +02:00
Johannes Zellner 0722d7ceb9 Update redis addon to set memory policy to noeviction 2024-10-15 22:07:17 +02:00
Johannes Zellner 06a23951c9 Use flexbox for sysinfo layout 2024-10-15 19:49:17 +02:00
Johannes Zellner 727d4876f5 Mobile fixes for volumes 2024-10-15 19:35:24 +02:00
Johannes Zellner f5a43786c2 Fix mobile view for services 2024-10-15 19:24:05 +02:00
Johannes Zellner 30967af8ec Fixup most mobile issues for eventlog 2024-10-15 19:19:16 +02:00
Johannes Zellner ccd892708b Hide wide summary for mail domains on mobile 2024-10-15 18:56:36 +02:00
Johannes Zellner 8cf3e38b27 Rework all section headers to deal with mobile wrapping 2024-10-15 18:46:51 +02:00
Johannes Zellner 4685f42045 fixup backups view for mobile 2024-10-15 17:50:46 +02:00
Johannes Zellner e6232189e7 use flexbox for appstore toolbar 2024-10-15 17:47:59 +02:00
Johannes Zellner 6e12d06343 Use flexbox for profile panel instead of old boostrap grid 2024-10-15 17:27:58 +02:00
Johannes Zellner d02b6d90cc Update translation 2024-10-15 15:51:27 +02:00
Johannes Zellner d10e9d7098 Fix api token list on mobile 2024-10-15 15:31:45 +02:00
Johannes Zellner 57b0cca6ab Give headers more space on mobile 2024-10-15 15:26:10 +02:00
Johannes Zellner fc565fd818 Give mobile navbar menu a shadow to elevate it from the content 2024-10-15 14:41:10 +02:00
Johannes Zellner 4e0c439c6f Close navbar in mobile if item is selected 2024-10-15 13:16:50 +02:00
Johannes Zellner 39220ba408 Do not remove all card padding on mobile 2024-10-15 13:05:33 +02:00
Girish Ramakrishnan 7fbb9f9df3 remove explicit encoding 2024-10-15 12:23:32 +02:00
Girish Ramakrishnan 6c3ca9c364 shell: rework code to use shell.spawn
spawn gives out streams and we have more control over the stdout/stderr
buffers. otherwise, we have to provide a max buffer capture size to exec
2024-10-15 12:13:46 +02:00
Girish Ramakrishnan 7b648cddfd shell: direct exports not needed anymore 2024-10-15 09:26:02 +02:00
Girish Ramakrishnan a9e1d7641d shell: make require take a tag 2024-10-14 21:08:32 +02:00
Girish Ramakrishnan 02823c4158 test: use same dashboard dir 2024-10-14 19:03:52 +02:00
Girish Ramakrishnan d58789cc25 test: more test fixing 2024-10-14 18:37:22 +02:00
Girish Ramakrishnan 434a0cba9f test: translation path has changed 2024-10-14 18:33:04 +02:00
Girish Ramakrishnan ca8695a1d3 typo 2024-10-14 18:26:16 +02:00
Girish Ramakrishnan 7f141605fa log the backuptask crash reason 2024-10-14 18:26:01 +02:00
Girish Ramakrishnan 23f9b5f2fc logs: when no timestamp, use the last known 2024-10-14 16:30:30 +02:00
Girish Ramakrishnan 1abbe43785 log exception with timestamp prefixed 2024-10-14 16:21:25 +02:00
Girish Ramakrishnan 6361737cf4 sudo: use debug() to have provide timestamped logs
the exception is when sudo calls backupupload.js which already has timestamped
output because it uses node

an alternative idea is to maybe not use this flag at all and always parse the output.
this is a bit complicated since we have to look for a timestamp in a stream.
2024-10-14 15:38:55 +02:00
Girish Ramakrishnan a884f968e1 syslog: fix parsing of multi-message packets 2024-10-14 13:54:32 +02:00
Johannes Zellner ce611c4773 dashboard: only open the cloudron detail page to create a subscription 2024-10-12 18:52:49 +02:00
Girish Ramakrishnan ba75c7ddaa porkbun: api endpoint has changed
https://porkbun.com/api/json/v3/documentation
2024-10-12 10:58:21 +02:00
Girish Ramakrishnan ff5dccc2b4 remove obsolete comment 2024-10-12 10:50:58 +02:00
Johannes Zellner 9b8994fe43 dashboard: fix changing views from appstore view after installation 2024-10-11 11:28:08 +02:00
Girish Ramakrishnan 34969d9980 groups: bump group_concat_max_len to accomdate more users 2024-10-09 19:12:53 +02:00
Johannes Zellner da11e90333 Static busy spinner for oidc login views 2024-10-09 13:14:43 +02:00
Johannes Zellner 282d06404e Static assets are actually on / 2024-10-09 12:56:25 +02:00
Johannes Zellner 64e60c106b Produce a static theme.css for oidc login views 2024-10-09 12:52:22 +02:00
Johannes Zellner 1b3fd20755 Fixup oidc pages to match new location of dashboard assets 2024-10-09 11:31:02 +02:00
Girish Ramakrishnan ce5a2b1f0a gandi: use PAT token instead
https://api.gandi.net/docs/authentication/
2024-10-08 17:51:01 +02:00
Johannes Zellner d68d5d5c51 Since dashboard is of type module we need to add correct common js extensions 2024-10-08 17:23:52 +02:00
Johannes Zellner 5a3460efb7 mimer.js is not used at all 2024-10-07 20:54:08 +02:00
Johannes Zellner edf5ddf027 Remove not required autofill polyfill 2024-10-07 20:50:01 +02:00
Johannes Zellner 982714fa4c We are not exporting this via git or so 2024-10-07 20:42:32 +02:00
Johannes Zellner 90ee525be7 Remove old unused dashboard scripts 2024-10-07 20:41:37 +02:00
Johannes Zellner 600323e027 Remove unused bootstrap files 2024-10-07 20:37:53 +02:00
Johannes Zellner 46a8b59196 Fixup mobile view for app list 2024-10-07 16:53:31 +02:00
Johannes Zellner f96ae1a1de mobile fixes for search and filter bar 2024-10-07 16:48:31 +02:00
Johannes Zellner 8894ec3019 Fix navbar menu with background set 2024-10-07 16:05:25 +02:00
Johannes Zellner 6f914a8d6b Handle scss files with vite also 2024-10-07 14:56:49 +02:00
Johannes Zellner 9f06b91399 Merge remaining frontend into dashboard 2024-10-04 21:37:17 +02:00
Johannes Zellner 9d7f12952d Move terminal.html to dashboard 2024-10-04 21:04:08 +02:00
Johannes Zellner bc4e6ab1de Move logs.html from frontend to dashboard 2024-10-04 20:47:49 +02:00
Johannes Zellner 2300e1baee Fully replace gulp with vite 2024-10-04 17:43:45 +02:00
Johannes Zellner 1b00e0f254 Multiselect now works with vite 2024-10-04 16:38:36 +02:00
Johannes Zellner 6534e99103 Make it possible to use a dynamic api endpoint for local development 2024-10-04 15:22:11 +02:00
Johannes Zellner ac98895e15 noto font is imported as module 2024-10-04 15:14:58 +02:00
Johannes Zellner 4e0961ae5a Translation files are now in public/ 2024-10-04 15:06:30 +02:00
Johannes Zellner 7669b77069 Some cleanup 2024-10-04 15:01:32 +02:00
Johannes Zellner 529d5b0b7b We use chartjs directly 2024-10-04 14:51:22 +02:00
Johannes Zellner 6edc482aad We don't target very old browsers anymore 2024-10-04 14:49:49 +02:00
Johannes Zellner 8fce81a264 Initial vite support for dashboard 2024-10-04 14:30:44 +02:00
Girish Ramakrishnan ea2479beda system: also get rota information 2024-09-30 14:09:15 +02:00
Johannes Zellner 40e7ee91d7 filemanager: fix new folder api call 2024-09-28 20:27:59 +02:00
Johannes Zellner 813942edbd Update frontend dependencies 2024-09-28 20:15:09 +02:00
Girish Ramakrishnan b70747de6f Add Cloudron Container Registry as option 2024-09-26 20:35:28 +02:00
Johannes Zellner 1c58f9aa5a dashboard: another small padding fix 2024-09-25 21:36:37 +02:00
Johannes Zellner 93aa2a4e6e dashboard: Better view-header padding 2024-09-25 17:26:43 +02:00
Girish Ramakrishnan 0504e0423a backups: add hetzner object storage 2024-09-25 12:21:42 +02:00
Girish Ramakrishnan c1c16ab54e test: add simple gitlab-ci file 2024-09-20 18:48:55 +02:00
831 changed files with 10558 additions and 24918 deletions
-3
View File
@@ -1,7 +1,4 @@
node_modules/ node_modules/
coverage/
.nyc_output/
webadmin/dist/
installer/src/certs/server.key installer/src/certs/server.key
# vim swap files # vim swap files
+24
View File
@@ -0,0 +1,24 @@
run_tests:
stage: test
image: cloudron/base:4.2.0@sha256:46da2fffb36353ef714f97ae8e962bd2c212ca091108d768ba473078319a47f4
services:
- name: mysql:8.0
alias: mysql
variables:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: box
BOX_ENV: ci
DATABASE_URL: mysql://root:password@mysql/box
script:
- echo "Running tests..."
- mysql -hmysql -uroot -ppassword -e "ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';"
- mysql -hmysql -uroot -ppassword -e "CREATE DATABASE IF NOT EXISTS box"
- npm install
- node_modules/.bin/db-migrate up
- ln -s /usr/local/node-18.18.0/bin/node /usr/bin/node
- node_modules/.bin/mocha --no-timeouts --bail src/test/tokens-test.js
- echo "Done!"
stages:
- test
+74
View File
@@ -2851,3 +2851,77 @@
[8.0.6] [8.0.6]
* Fix AdGuard resolving dashboard to docker bridge IP * Fix AdGuard resolving dashboard to docker bridge IP
[8.1.0]
* backups: add hetzner object storage
* registry: cloudron container registry
* gandi: add PAT token support
* OpenID: add groups claim support
* OpenID: enable refresh token support (dokuwiki)
* filemanger: fix various regressions
* dashboard: mobile and dark-mode fixes
* syslog: fix multiline timestamps
* porkbun: use new API endpoint
* fix "happy eyeballs" quirk in nodejs
* Update nodejs to 20.18.0
[8.2.0]
* rsync: show better error message with too many empty dirs, symlinks or executables
* mail: update Solr to 8.11.4
* mail: update Haraka to 3.0.5
* Add sqlite3 addon
* docker: update docker to 27.3.1
* du: add exclude file to skip filesystem usage checks
* mail: attachment search
* oidc: use cloudron name as provider name
* groups: add eventlog
* resources: allow mounting devices into apps
* remove global lock
* hetzner: add helsinki object storage location
* backups: implement app archive
* notifications: per user email notification config
* postgres: enable vector extension
* docker: fallback to downloading images from quay if dockerhub does not work
[8.2.1]
* apps: fix bug where update and notes indicator was shown to normal users
* archive: disable archiving for pre-8.2 backups. we don't have enough info to unarchive
* dashboard: fix browser caching issue
[8.2.2]
* gandi: add token type in the setup view
* mail: fix issue with dkim signing
* mail: fix crash in dns list plugin
* scheduler: create jobs with cloudron tz setting
* security: fix issue where '/' symlink allows admins to get ssh access
[8.2.3]
* mail: give container a static IP
* firewall: add masquerading rules for containers to reach each other via public IP
* docker: fix parsing of optional namespace in image refs
[8.2.4]
* restore: fix crash with invalid backup id
* setup: add inwx to dns setup
* backups: add preserve attributes checkbox
* mail: add ipv6 rdns check
* mail: disable OCR in tika. this is too slow
* mail: rebuild index script
* backups: add preserve attributes checkbox
* username: only ending with .app is reserved
* cloudron-support: add helper function to free up disk space when full
* cloudflare: list API does not return `zone_id` anymore
[8.3.0]
* new base image: cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
* Database upgrades are automatically performed. This might take some time depending on the amount of data.
* Postgres v16
* Mongodb v7
* PHP v8.3
* Node.js v22 LTS
[8.3.1]
* Fix crash in postgresql pgvector extension
[8.3.2]
* Bring back immich vectors hook in postgres addon
+6 -7
View File
@@ -45,20 +45,19 @@ Try our demo at https://my.demo.cloudron.io (username: cloudron password: cloudr
[Install script](https://docs.cloudron.io/installation/) - [Pricing](https://cloudron.io/pricing.html) [Install script](https://docs.cloudron.io/installation/) - [Pricing](https://cloudron.io/pricing.html)
**Note:** This repo is a small part of what gets installed on your server - there is **Note:** This repo is just a part of what gets installed on the server. Database addons,
the dashboard, database addons, graph container, base image etc. Cloudron also relies Mail Server, Stat contains etc are not part of this repo. As such, don't clone this repo and
on external services such as the App Store for apps to be installed. As such, don't npm install and expect something to work.
clone this repo and npm install and expect something to work.
## License ## License
Please note that the Cloudron code is under a source-available license. This is not the same as an Please note that the Cloudron code is under a source-available license. This is not the same as an
open source license but ensures the code is available for introspection (and hacking!). open source license but ensures the code is available for transparency and introspection (and hacking!).
## Contributions ## Contributions
Just to give some heads up, we are a bit restrictive in merging changes. We are a small team and We are very restrictive in merging changes. We are a small team and would like to keep our maintenance burden low,
would like to keep our maintenance burden low. It might be best to discuss features first in the [forum](https://forum.cloudron.io), not to mention legal issues. It might be best to discuss features first in the [forum](https://forum.cloudron.io),
to also figure out how many other people will use it to justify maintenance for a feature. to also figure out how many other people will use it to justify maintenance for a feature.
# Localization # Localization
+11 -1
View File
@@ -5,6 +5,7 @@
const constants = require('./src/constants.js'), const constants = require('./src/constants.js'),
fs = require('fs'), fs = require('fs'),
ldapServer = require('./src/ldapserver.js'), ldapServer = require('./src/ldapserver.js'),
net = require('net'),
oidc = require('./src/oidc.js'), oidc = require('./src/oidc.js'),
paths = require('./src/paths.js'), paths = require('./src/paths.js'),
proxyAuth = require('./src/proxyauth.js'), proxyAuth = require('./src/proxyauth.js'),
@@ -25,9 +26,17 @@ async function setupLogging() {
}; };
} }
// happy eyeballs workaround. when there is no ipv6, nodejs timesout prematurely since the default for ipv4 is just 250ms
// https://github.com/nodejs/node/issues/54359
async function setupNetworking() {
net.setDefaultAutoSelectFamilyAttemptTimeout(2500);
}
// this is also used as the 'uncaughtException' handler which can only have synchronous functions // this is also used as the 'uncaughtException' handler which can only have synchronous functions
function exitSync(status) { function exitSync(status) {
if (status.error) fs.write(logFd, status.error.stack + '\n', function () {}); const ts = new Date().toISOString();
const msg = status.error.stack.replace(/\n/g, `\n${ts} `); // prefix each line with ts
if (status.error) fs.write(logFd, `${ts} ${msg}\n`, function () {});
fs.fsyncSync(logFd); fs.fsyncSync(logFd);
fs.closeSync(logFd); fs.closeSync(logFd);
process.exit(status.code); process.exit(status.code);
@@ -35,6 +44,7 @@ function exitSync(status) {
async function startServers() { async function startServers() {
await setupLogging(); await setupLogging();
await setupNetworking();
await server.start(); // do this first since it also inits the database await server.start(); // do this first since it also inits the database
await proxyAuth.start(); await proxyAuth.start();
await ldapServer.start(); await ldapServer.start();
-6
View File
@@ -1,6 +0,0 @@
# following files are skipped when exporting using git archive
test export-ignore
docs export-ignore
.gitattributes export-ignore
.gitignore export-ignore
+9 -4
View File
@@ -1,11 +1,16 @@
dist/ dist/
node_modules/ node_modules/
# will get generated on build
public/theme.css
public/theme.css.map
# vim swap files # vim swap files
*.swp *.swp
# these are not done yet # these are not done yet
src/translation/ja.json public/translation/ja.json
src/translation/pl.json public/translation/pl.json
src/translation/si.json public/translation/si.json
src/translation/gl.json public/translation/gl.json
public/translation/hr.json
-20
View File
@@ -1,20 +0,0 @@
{
"node": true,
"browser": true,
"unused": true,
"esversion": 6,
"globalstrict": false,
"predef": [
"$",
"angular",
"async",
"describe",
"it",
"before",
"after",
"require",
"monaco",
"Mimer",
"ISTATES"
]
}
-35
View File
@@ -1,35 +0,0 @@
The Cloudron Subscription license
Copyright (c) 2022 Cloudron UG
With regard to the Cloudron Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cloudron Subscription Terms of Service, available
at https://cloudron.io/legal/terms.html (the “Subscription Terms”), or other
agreement governing the use of the Software, as agreed by you and Cloudron,
and otherwise have a valid Cloudron Subscription. Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Subscription and/or its licensors (as applicable) retain all right, title and
interest in and to all such modifications and/or patches, and all such modifications
and/or patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Cloudron subscription. Notwithstanding the foregoing, you may copy
and modify the Software for development and testing purposes, without requiring a
subscription. You agree that Cloudron and/or its licensors (as applicable) retain
all right, title and interest in and to all such modifications. You are not
granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
For all third party components incorporated into the Cloudron Software, those
components are licensed under the original license provided by the owner of the
applicable component.
-20
View File
@@ -1,20 +0,0 @@
# Cloudron Dashboard
This is the front end code of Cloudron. The backend code is [here](https://git.cloudron.io/cloudron/box).
## Developing
* `npm install`
* `gulp develop --api-origin=https://my.example.com`
## License
Please note that the Cloudron code is under a source-available license. This is not the same as an
open source license but ensures the code is available for inspection (and hacking!).
## Contributions
Just to give a heads-up, we are a bit restrictive in merging changes. We are a small team and
would like to keep our maintenance burden low. It might be best to first discuss features in the [forum](https://forum.cloudron.io),
which also helps to determine how many other people will use it to justify maintenance for a feature.
@@ -9,43 +9,41 @@
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png"> <link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
<!-- Theme CSS --> <!-- contains all thing already using import statement -->
<link type="text/css" rel="stylesheet" href="/theme.css"> <script type="module" src="./src/modules.js"></script>
<!-- Fontawesome --> <!-- Theme CSS -->
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.min.css?<%= revision %>"/> <link type="text/css" rel="stylesheet" href="./src/theme.scss">
<!-- jQuery--> <!-- jQuery-->
<script type="text/javascript" src="/3rdparty/js/jquery.min.js"></script> <script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- async --> <!-- async -->
<script type="text/javascript" src="/3rdparty/js/async-3.2.0.min.js"></script> <script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js"></script>
<!-- Angularjs scripts --> <!-- Angularjs scripts -->
<script type="text/javascript" src="/3rdparty/js/angular.min.js"></script> <script type="text/javascript" src="/js/angular.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js"></script> <script type="text/javascript" src="/js/angular-loader.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-cookies.min.js"></script> <script type="text/javascript" src="/js/angular-cookies.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-md5.min.js"></script> <script type="text/javascript" src="/js/angular-md5.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js"></script> <script type="text/javascript" src="/js/angular-ui-notification.js"></script>
<script type="text/javascript" src="/3rdparty/js/autofill-event.js"></script>
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ --> <!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
<script type="text/javascript" src="/3rdparty/js/ui-bootstrap-tpls-1.3.3.min.js"></script> <script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></script>
<!-- Angular translate https://angular-translate.github.io/ --> <!-- Angular translate https://angular-translate.github.io/ -->
<script type="text/javascript" src="/3rdparty/js/angular-translate.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular-translate.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-loader-static-files.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-cookie.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-local.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
<!-- Showdown (markdown converter) --> <!-- Showdown (markdown converter) -->
<script type="text/javascript" src="/3rdparty/js/showdown-1.9.1.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
<!-- Setup Application --> <!-- Setup Application -->
<script type="text/javascript" src="/js/activation.js"></script> <script type="text/javascript" src="/js/activation.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/js/client.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
</head> </head>
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
set -eu
echo "=> Create timezones.js"
./scripts/createTimezones.cjs ./public/js/timezones.js
echo "=> Build theme.css for oidc views"
./node_modules/.bin/sass --quiet --pkg-importer=node ./src/theme.scss ./public/theme.css
export VITE_CACHE_ID=$(date +%s)
echo "=> Build the dashboard apps"
./node_modules/.bin/vite build
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -eu
echo "=> Set API origin"
export VITE_API_ORIGIN="${DASHBOARD_DEVELOPMENT_ORIGIN}"
# only really used for prod builds to bust cache
export VITE_CACHE_ID="develop"
echo "=> Run vite locally"
npm run dev
-217
View File
@@ -1,217 +0,0 @@
/* jslint node:true */
'use strict';
const argv = require('yargs').argv,
concat = require('gulp-concat'),
ejs = require('gulp-ejs'),
execSync = require('child_process').execSync,
fs = require('fs'),
gulp = require('gulp'),
sass = require('gulp-sass')(require('sass')),
serve = require('gulp-serve'),
sourcemaps = require('gulp-sourcemaps');
if (argv.help || argv.h) {
console.log('Supported arguments for "gulp develop":');
console.log(' --api-origin <cloudron api uri>');
console.log(' --revision <revision>');
console.log(' --appstore-console-origin <appstore console uri>');
process.exit(1);
}
const revision = argv.revision || '';
let apiOrigin = '';
if (argv.apiOrigin) {
if (argv.apiOrigin.indexOf('https://') === 0) apiOrigin = argv.apiOrigin;
else apiOrigin = 'https://' + argv.apiOrigin;
}
var appstore = {
consoleOrigin: argv.appstoreConsoleOrigin || ''
};
console.log();
console.log('Cloudron API: %s', apiOrigin || 'default');
console.log('Building for revision: %s', revision);
console.log();
console.log('Overriding appstore origin:');
console.log(' Console: %s', appstore.consoleOrigin || 'no');
console.log();
gulp.task('fontawesome', function () {
return gulp.src('node_modules/@fortawesome/fontawesome-free/**/*')
.pipe(gulp.dest('dist/3rdparty/fontawesome/'));
});
gulp.task('noto-sans', function () {
return gulp.src('node_modules/@fontsource/noto-sans/**/*')
.pipe(gulp.dest('dist/3rdparty/noto-sans/'));
});
gulp.task('bootstrap', function () {
return gulp.src('node_modules/bootstrap-sass/assets/javascripts/bootstrap.min.js')
.pipe(gulp.dest('dist/3rdparty/js'));
});
gulp.task('moment', function () {
return gulp.src('node_modules/moment/min/*')
.pipe(gulp.dest('dist/3rdparty/js'));
});
gulp.task('3rdparty-copy', function () {
return gulp.src([
'src/3rdparty/**/*.js',
'src/3rdparty/**/*.map',
'src/3rdparty/**/*.css',
'src/3rdparty/**/*.otf',
'src/3rdparty/**/*.eot',
'src/3rdparty/**/*.svg',
'src/3rdparty/**/*.gif',
'src/3rdparty/**/*.ttf',
'node_modules/chart.js/dist/chart.umd.js'
]).pipe(gulp.dest('dist/3rdparty/'));
});
gulp.task('3rdparty', gulp.series(['3rdparty-copy', 'moment', 'bootstrap', 'fontawesome', 'noto-sans']));
// --------------
// JavaScript
// --------------
gulp.task('js-index', function () {
return gulp.src([
'src/js/index.js',
'src/js/client.js',
'src/js/utils.js',
'src/views/*.js'
])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('index.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js-passwordreset', function () {
return gulp.src(['src/js/passwordreset.js', 'src/js/utils.js'])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('passwordreset.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js-setupaccount', function () {
return gulp.src(['src/js/setupaccount.js', 'src/js/utils.js'])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('setupaccount.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js-activation', function () {
return gulp.src(['src/js/activation.js', 'src/js/client.js', 'src/js/utils.js'])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('activation.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js-setup', function () {
return gulp.src(['src/js/setup.js', 'src/js/client.js', 'src/js/utils.js'])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('setup.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js-restore', function () {
return gulp.src(['src/js/restore.js', 'src/js/client.js', 'src/js/utils.js'])
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
.pipe(sourcemaps.init())
.pipe(concat('restore.js', { newLine: ';' }))
.pipe(sourcemaps.write())
.pipe(gulp.dest('dist/js'));
});
gulp.task('js', gulp.series([ 'js-index', 'js-passwordreset', 'js-setupaccount', 'js-activation', 'js-setup', 'js-restore' ]));
// --------------
// HTML
// --------------
gulp.task('html-views', function () {
return gulp.src('src/views/**/*.html').pipe(gulp.dest('dist/views'));
});
gulp.task('html-raw', function () {
return gulp.src('src/*.html').pipe(ejs({ apiOrigin: apiOrigin, revision: revision }, {}, { ext: '.html' })).pipe(gulp.dest('dist'));
});
gulp.task('html', gulp.series(['html-views', 'html-raw']));
// --------------
// CSS
// --------------
gulp.task('css', function () {
return gulp.src('src/*.scss')
.pipe(sass({ includePaths: [
'node_modules/bootstrap-sass/assets/stylesheets/',
'node_modules/@fontsource/'
]}).on('error', sass.logError))
.pipe(gulp.dest('dist'));
});
gulp.task('images', function () {
return gulp.src('src/img/**')
.pipe(gulp.dest('dist/img'));
});
gulp.task('translation', function () {
return gulp.src('src/translation/**')
.pipe(gulp.dest('dist/translation'));
});
gulp.task('timezones', function (done) {
execSync('./scripts/createTimezones.js ./dist/js/timezones.js');
done();
});
// --------------
// Utilities
// --------------
gulp.task('clean', function (done) {
fs.rm('dist', { recursive: true, force: true }, done);
});
gulp.task('default', gulp.series(['clean', 'html', 'js', 'timezones', '3rdparty', 'translation', 'images', 'css']));
gulp.task('watch', function (done) {
gulp.watch(['src/*.scss'], gulp.series(['css']));
gulp.watch(['src/img/*'], gulp.series(['images']));
gulp.watch(['src/translation/*'], gulp.series(['translation']));
gulp.watch(['src/**/*.html'], gulp.series(['html']));
gulp.watch(['src/views/*.html'], gulp.series(['html-views']));
gulp.watch(['scripts/createTimezones.js', 'src/js/utils.js'], gulp.series(['timezones']));
gulp.watch(['src/js/activation.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-activation']));
gulp.watch(['src/js/setup.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-setup']));
gulp.watch(['src/js/restore.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-restore']));
gulp.watch(['src/js/passwordreset.js', 'src/js/utils.js'], gulp.series(['js-passwordreset']));
gulp.watch(['src/js/setupaccount.js', 'src/js/utils.js'], gulp.series(['js-setupaccount']));
gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/views/*.js', 'src/js/utils.js'], gulp.series(['js-index']));
gulp.watch(['src/3rdparty/**/*'], gulp.series(['3rdparty']));
done();
});
gulp.task('serve', serve({ root: 'dist', port: 4000, hostname: '0.0.0.0' }));
gulp.task('develop', gulp.series(['default', 'watch', 'serve']));
+211
View File
@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html ng-app="Application" ng-controller="MainController">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<!-- this gets changed once we get the config (because angular has not loaded yet, we see template string for a flash) -->
<title>Cloudron Dashboard</title>
<meta name="description" content="Cloudron Dashboard">
<link id="favicon" type="image/png" rel="icon" href="/api/v1/cloudron/avatar">
<link rel="apple-touch-icon" href="/api/v1/cloudron/avatar">
<link rel="icon" href="/api/v1/cloudron/avatar">
<!-- contains all thing already using import statement -->
<script type="module" src="./src/modules.js"></script>
<!-- jQuery-->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- CSS -->
<link type="text/css" rel="stylesheet" href="/slick.css"/>
<link type="text/css" rel="stylesheet" href="/angular-ui-notification.css"/>
<link type="text/css" rel="stylesheet" href="./src/theme.scss">
<!-- async -->
<script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
<!-- Slick carousel -->
<script type="text/javascript" src="/js/slick.js"></script>
<!-- Angularjs scripts -->
<script type="text/javascript" src="/js/angular.min.js"></script>
<script type="text/javascript" src="/js/angular-loader.min.js"></script>
<script type="text/javascript" src="/js/angular-route.min.js"></script>
<script type="text/javascript" src="/js/angular-cookies.min.js"></script>
<script type="text/javascript" src="/js/angular-animate.min.js"></script>
<script type="text/javascript" src="/js/angular-base64.min.js"></script>
<script type="text/javascript" src="/js/angular-md5.min.js"></script>
<script type="text/javascript" src="/js/angular-sanitize.min.js"></script>
<script type="text/javascript" src="/js/angular-slick.min.js"></script>
<script type="text/javascript" src="/js/angular-ui-notification.js"></script>
<script type="text/javascript" src="/js/angular-fittext.min.js"></script>
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
<script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></script>
<!-- Angular translate https://angular-translate.github.io/ -->
<script type="text/javascript" src="/js/angular-translate.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
<script type="text/javascript" src="/js/clipboard.min.js"></script>
<!-- Showdown (markdown converter) -->
<script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
<!-- Anugular Multiselect https://github.com/sebastianha/angular-bootstrap-multiselect -->
<script type="text/javascript" src="/js/angular-bootstrap-multiselect.js"></script>
<!-- timezone list -->
<script type="text/javascript" src="/js/timezones.js?%VITE_CACHE_ID%"></script>
<!-- Main Application -->
<!-- for now we need this in a static non transformed index.js file -->
<script> window.VITE_CACHE_ID = '%VITE_CACHE_ID%' </script>
<script type="text/javascript" src="/js/index.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/js/client.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/app.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/apps.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/appstore.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/backups.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/branding.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/domains.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/email.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/emails-eventlog.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/emails.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/emails-queue.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/eventlog.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/network.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/notifications.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/profile.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/services.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/settings.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/support.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/system.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/user-directory.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/users.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/views/volumes.js?%VITE_CACHE_ID%"></script>
</head>
<body>
<script type="text/ng-template" id="notification.html">
<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>
</script>
<a class="offline-banner animateMe" ng-show="client.offline" ng-cloak href="https://docs.cloudron.io/troubleshooting/" target="_blank"><i class="fa fa-circle-notch fa-spin"></i> {{ 'main.offline' | tr }}</a>
<!-- Modal reboot server -->
<div class="modal fade" id="rebootModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'main.rebootDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<p class="text-bold">{{ 'main.rebootDialog.warning' | tr }}</p>
<p>{{ 'main.rebootDialog.description' | tr }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="reboot.submit()" ng-disabled="reboot.busy"><i class="fa fa-circle-notch fa-spin" ng-show="reboot.busy"></i> {{ 'main.rebootDialog.rebootAction' | tr }}</button>
</div>
</div>
</div>
</div>
<div id="mainContentContainer" class="animateMe ng-hide layout-root" ng-show="initialized">
<!-- Navigation -->
<nav class="navbar navbar-default navbar-static-top shadow" role="navigation" style="margin-bottom: 0">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand navbar-brand-icon" href="#/"><img ng-src="{{ client.avatar }}" width="40" height="40"/></a>
<a class="navbar-brand" href="#/">{{ config.cloudronName || 'Cloudron' }}</a>
</div>
<!-- /.navbar-header -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right" ng-hide="hideNavBarActions">
<li ng-show="user.isAtLeastOwner && (subscription.plan.id === 'free' || subscription.plan.id === 'expired')">
<a ng-click="openSubscriptionSetup()" style="cursor: pointer">
<span class="badge" ng-class="{'badge-danger': subscription.plan.id !== 'free', 'badge-success': subscription.plan.id === 'free' }">
{{ subscription.plan.id === 'free' ? ('settings.appstoreAccount.subscriptionSetupAction' | tr) : ('settings.appstoreAccount.subscriptionReactivateAction' | tr) }}
</span>
</a>
</li>
<li ng-show="!user.isAtLeastOwner && subscription.plan.id === 'expired'">
<a>
<span class="badge badge-danger">Subscription Expired</span>
</a>
</li>
<li>
<a ng-class="{ active: isActive('/apps')}" href="#/apps" ng-click="closeNavbar()"><i class="fa fa-grip fa-fw"></i> {{ 'apps.title' | tr }}</a>
</li>
<li ng-show="user.isAtLeastAdmin">
<a ng-class="{ active: isActive('/appstore')}" href="#/appstore" ng-click="closeNavbar()"><i class="fa fa-cloud-download-alt fa-fw"></i> {{ 'appstore.title' | tr }}</a>
</li>
<li ng-show="user.isAtLeastUserManager">
<a ng-class="{ active: isActive('/users')}" href="#/users" ng-click="closeNavbar()"><i class="fa fa-users fa-fw"></i> {{ 'main.navbar.users' | tr }}</a>
</li>
<li ng-show="user.isAtLeastAdmin">
<a href="#/notifications" ng-click="closeNavbar()">
<i class="fas fa-bell" ng-show="notificationCount"></i>
<i class="far fa-bell" ng-hide="notificationCount"></i>
<span class="badge badge-danger" ng-show="notificationCount">{{ notificationCount === 100 ? '100+' : notificationCount }}</span>
</a>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><img ng-src="{{user.avatarUrl}}" style="width: 24px; height: 24px;"/> {{user.username}} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="#/profile" ng-click="closeNavbar()"><i class="fa fa-user fa-fw"></i> {{ 'profile.title' | tr }}</a></li>
<li ng-show="user.isAtLeastMailManager" class="divider"></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/backups" ng-click="closeNavbar()"><i class="fa fa-archive fa-fw"></i> {{ 'backups.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/branding" ng-click="closeNavbar()"><i class="fa fa-passport fa-fw"></i> {{ 'branding.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/domains" ng-click="closeNavbar()"><i class="fa fa-globe fa-fw"></i> {{ 'domains.title' | tr }}</a></li>
<li ng-show="user.isAtLeastMailManager"><a href="#/email" ng-click="closeNavbar()"><i class="fa fa-envelope fa-fw"></i> {{ 'emails.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/eventlog" ng-click="closeNavbar()"><i class="fa fa-list-alt fa-fw"></i> {{ 'eventlog.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/network" ng-click="closeNavbar()"><i class="fas fa-network-wired fa-fw"></i> {{ 'network.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/services" ng-click="closeNavbar()"><i class="fa fa-cogs fa-fw"></i> {{ 'services.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/settings" ng-click="closeNavbar()"><i class="fa fa-wrench fa-fw"></i> {{ 'settings.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/user-directory" ng-click="closeNavbar()"><i class="fa fa-users-gear fa-fw"></i> {{ 'users.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/volumes" ng-click="closeNavbar()"><i class="fa fa-hdd fa-fw"></i> {{ 'volumes.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin" class="divider"></li>
<li ng-show="user.isAtLeastOwner"><a href="#/support" ng-click="closeNavbar()"><i class="fa fa-comment fa-fw"></i> {{ 'support.title' | tr }}</a></li>
<li ng-show="user.isAtLeastAdmin"><a href="#/system" ng-click="closeNavbar()"><i class="fa fa-chart-area fa-fw"></i> {{ 'system.title' | tr }}</a></li>
<li class="divider"></li>
<li><a href="" ng-click="logout($event)"><i class="fa fa-sign-out-alt fa-fw"></i> {{ 'main.logout' | tr }}</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div ng-view id="ng-view" class="layout-content"></div>
<footer class="text-center ng-cloak">
<span class="text-muted" ng-bind-html="config.footer | markdown2html"></span>
</footer>
</div>
</body>
</html>
+4215 -8676
View File
File diff suppressed because it is too large Load Diff
+25 -27
View File
@@ -1,35 +1,33 @@
{ {
"name": "dashboard", "private": true,
"version": "1.0.0",
"description": "[Cloudron](https://cloudron.io) is the best way to run apps on your server.",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "update-translations": "curl https://translate.cloudron.io/api/components/cloudron/dashboard/file/ -o lang.zip && unzip -jo lang.zip -d ./public/translation/ && rm lang.zip",
"update-translations": "curl https://translate.cloudron.io/api/components/cloudron/dashboard/file/ -o lang.zip && unzip -jo lang.zip -d ./src/translation/ && rm lang.zip" "dev": "vite --strictPort --port 4000",
"build": "vite build"
}, },
"repository": { "type": "module",
"type": "git",
"url": "ssh://git@git.cloudron.io:6000/cloudron/dashboard.git"
},
"author": "",
"license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"@fontsource/noto-sans": "^5.0.21", "@eslint/js": "^9.16.0",
"@fortawesome/fontawesome-free": "^6.5.2", "@fontsource/noto-sans": "^5.1.0",
"@fortawesome/fontawesome-free": "^6.7.1",
"@vitejs/plugin-vue": "^5.2.1",
"@xterm/addon-attach": "^0.11.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"anser": "^2.3.0",
"bootstrap-sass": "^3.4.3", "bootstrap-sass": "^3.4.3",
"chart.js": "^4.4.2", "chart.js": "^4.4.7",
"gulp": "^4.0.2", "eslint-plugin-vue": "^9.32.0",
"gulp-concat": "^2.6.1", "filesize": "^10.1.6",
"gulp-ejs": "^5.1.0", "jquery": "^3.7.1",
"gulp-sass": "^5.1.0", "marked": "^15.0.3",
"gulp-serve": "^1.4.0",
"gulp-sourcemaps": "^3.0.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"sass": "^1.75.0", "pankow": "^2.4.2",
"yargs": "^17.7.2" "pankow-viewers": "^1.0.11",
}, "sass": "^1.82.0",
"eslintConfig": { "vite": "^6.0.3",
"env": { "vue": "^3.5.13",
"browser": true "vue-i18n": "^10.0.5",
} "vue-router": "^4.5.0"
} }
} }
@@ -1,53 +1,47 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" /> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<meta http-equiv="Content-Security-Policy" content="default-src <%= apiOrigin %> 'unsafe-inline' 'unsafe-eval' 'self'; img-src <%= apiOrigin %> 'self'" />
<!-- this gets changed once we get the status (because angular has not loaded yet, we see template string for a flash) --> <!-- this gets changed once we get the status (because angular has not loaded yet, we see template string for a flash) -->
<title>Cloudron Password Reset</title> <title>Cloudron Password Reset</title>
<meta name="description" content="Cloudron Password Reset"> <meta name="description" content="Cloudron Password Reset">
<link id="favicon" href="<%= apiOrigin %>/api/v1/cloudron/avatar" rel="icon" type="image/png"> <link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
<!-- Theme CSS --> <!-- contains all thing already using import statement -->
<link type="text/css" rel="stylesheet" href="/theme.css?<%= revision %>"> <script type="module" src="./src/modules.js"></script>
<!-- Fontawesome --> <!-- Theme CSS -->
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.min.css?<%= revision %>"/> <link type="text/css" rel="stylesheet" href="./src/theme.scss">
<!-- jQuery--> <!-- jQuery-->
<script type="text/javascript" src="/3rdparty/js/jquery.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/jquery.min.js"></script>
<!-- async --> <!-- async -->
<script type="text/javascript" src="/3rdparty/js/async-3.2.0.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/async-3.2.0.min.js"></script>
<!-- Bootstrap Core JavaScript --> <!-- Showdown (markdown converter) -->
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/showdown-1.9.1.min.js"></script>
<!-- Showdown (markdown converter) --> <!-- Angularjs scripts -->
<script type="text/javascript" src="/3rdparty/js/showdown-1.9.1.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular.min.js"></script>
<script type="text/javascript" src="/js/angular-loader.min.js"></script>
<script type="text/javascript" src="/js/angular-cookies.min.js"></script>
<!-- Angularjs scripts --> <!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
<script type="text/javascript" src="/3rdparty/js/angular.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/ui-bootstrap-tpls-1.3.3.min.js"></script>
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js?<%= revision %>"></script>
<!-- <script type="text/javascript" src="/3rdparty/js/angular-md5.min.js"></script> -->
<!-- <script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js"></script> -->
<script type="text/javascript" src="/3rdparty/js/angular-cookies.min.js?<%= revision %>"></script>
<script type="text/javascript" src="/3rdparty/js/autofill-event.js?<%= revision %>"></script>
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ --> <!-- Angular translate https://angular-translate.github.io/ -->
<script type="text/javascript" src="/3rdparty/js/ui-bootstrap-tpls-1.3.3.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/angular-translate.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-loader-static-files.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-storage-cookie.min.js"></script>
<script type="text/javascript" src="/js/angular-translate-storage-local.min.js"></script>
<!-- Angular translate https://angular-translate.github.io/ --> <!-- Setup Application -->
<script type="text/javascript" src="/3rdparty/js/angular-translate.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/passwordreset.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-loader-static-files.min.js?<%= revision %>"></script> <script type="text/javascript" src="/js/utils.js?%VITE_CACHE_ID%"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-cookie.min.js?<%= revision %>"></script>
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-local.min.js?<%= revision %>"></script>
<!-- Setup Application -->
<script type="text/javascript" src="/js/passwordreset.js?<%= revision %>"></script>
</head> </head>
@@ -59,7 +53,7 @@
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;"> <div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
<div class="row"> <div class="row">
<div class="col-md-12" style="text-align: center;"> <div class="col-md-12" style="text-align: center;">
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/> <img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
<br/> <br/>
<h2>{{ 'passwordReset.title' | tr }}</h2> <h2>{{ 'passwordReset.title' | tr }}</h2>
</div> </div>
@@ -87,7 +81,7 @@
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;"> <div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
<div class="row"> <div class="row">
<div class="col-md-12" style="text-align: center;"> <div class="col-md-12" style="text-align: center;">
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/> <img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
<br/> <br/>
<h2 ng-hide="error">{{ 'passwordReset.emailSent.title' | tr }}</h2> <h2 ng-hide="error">{{ 'passwordReset.emailSent.title' | tr }}</h2>
<h4 ng-show="error" class="has-error">{{ error }}</h4> <h4 ng-show="error" class="has-error">{{ error }}</h4>
@@ -102,7 +96,7 @@
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;"> <div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
<div class="row"> <div class="row">
<div class="col-md-12" style="text-align: center;"> <div class="col-md-12" style="text-align: center;">
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/> <img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
<br/> <br/>
<h2>{{ 'passwordReset.newPassword.title' | tr }}</h2> <h2>{{ 'passwordReset.newPassword.title' | tr }}</h2>
</div> </div>
@@ -150,7 +144,7 @@
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;"> <div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
<div class="row"> <div class="row">
<div class="col-md-12" style="text-align: center;"> <div class="col-md-12" style="text-align: center;">
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/> <img width="128" height="128" style="margin-top: -84px" src="/api/v1/cloudron/avatar"/>
<br/> <br/>
<h2>{{ 'passwordReset.success.title' | tr }}</h2> <h2>{{ 'passwordReset.success.title' | tr }}</h2>
<br/> <br/>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,14 +1,13 @@
'use strict'; 'use strict';
/* global angular, window, document, localStorage, redirectIfNeeded */ /* global $, angular, redirectIfNeeded */
/* global $ */
// create main application module // create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']); var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
app.controller('SetupController', ['$scope', 'Client', function ($scope, Client) { app.controller('SetupController', ['$scope', 'Client', function ($scope, Client) {
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own // 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; }, {}); const 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; }, {});
$scope.client = Client; $scope.client = Client;
$scope.view = ''; $scope.view = '';
@@ -1,26 +1,23 @@
'use strict'; 'use strict';
/* global $ */ /* global $, angular, async */
/* global angular */
/* global EventSource */
/* global async */
// keep in sync with box/src/notfications.js // keep in sync with box/src/notfications.js
const NOTIFICATION_TYPES = { const NOTIFICATION_TYPES = {
ALERT_CLOUDRON_INSTALLED: 'cloudronInstalled', CLOUDRON_INSTALLED: 'cloudronInstalled',
ALERT_CLOUDRON_UPDATED: 'cloudronUpdated', CLOUDRON_UPDATED: 'cloudronUpdated',
ALERT_CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed', CLOUDRON_UPDATE_FAILED: 'cloudronUpdateFailed',
ALERT_CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed', CERTIFICATE_RENEWAL_FAILED: 'certificateRenewalFailed',
ALERT_BACKUP_CONFIG: 'backupConfig', BACKUP_CONFIG: 'backupConfig',
ALERT_DISK_SPACE: 'diskSpace', DISK_SPACE: 'diskSpace',
ALERT_MAIL_STATUS: 'mailStatus', MAIL_STATUS: 'mailStatus',
ALERT_REBOOT: 'reboot', REBOOT: 'reboot',
ALERT_BOX_UPDATE: 'boxUpdate', BOX_UPDATE: 'boxUpdate',
ALERT_UPDATE_UBUNTU: 'ubuntuUpdate', UPDATE_UBUNTU: 'ubuntuUpdate',
ALERT_MANUAL_APP_UPDATE: 'manualAppUpdate', MANUAL_APP_UPDATE: 'manualAppUpdate',
ALERT_APP_OOM: 'appOutOfMemory', APP_OOM: 'appOutOfMemory',
ALERT_APP_UPDATED: 'appUpdated', APP_UPDATED: 'appUpdated',
ALERT_BACKUP_FAILED: 'backupFailed', BACKUP_FAILED: 'backupFailed',
}; };
// keep in sync with box/src/apps.js // keep in sync with box/src/apps.js
@@ -167,6 +164,12 @@ const REGIONS_WASABI = [
{ name: 'Virginia (US East 2)', value: 'https://s3.us-east-2.wasabisys.com' } { name: 'Virginia (US East 2)', value: 'https://s3.us-east-2.wasabisys.com' }
]; ];
const REGIONS_HETZNER = [
{ name: 'Falkenstein (FSN1)', value: 'https://fsn1.your-objectstorage.com' },
{ name: 'Helsinki (HEL1)', value: 'https://hel1.your-objectstorage.com' },
{ name: 'Nuremberg (NBG1)', value: 'https://nbg1.your-objectstorage.com' }
];
// https://docs.digitalocean.com/products/platform/availability-matrix/ // https://docs.digitalocean.com/products/platform/availability-matrix/
const REGIONS_DIGITALOCEAN = [ const REGIONS_DIGITALOCEAN = [
{ name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' }, { name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
@@ -292,6 +295,7 @@ const STORAGE_PROVIDERS = [
{ name: 'Filesystem', value: 'filesystem' }, { name: 'Filesystem', value: 'filesystem' },
{ name: 'Filesystem (Mountpoint)', value: 'mountpoint' }, // legacy { name: 'Filesystem (Mountpoint)', value: 'mountpoint' }, // legacy
{ name: 'Google Cloud Storage', value: 'gcs' }, { name: 'Google Cloud Storage', value: 'gcs' },
{ name: 'Hetzner Object Storage', value: 'hetzner-objectstorage' },
{ name: 'IDrive e2', value: 'idrive-e2' }, { name: 'IDrive e2', value: 'idrive-e2' },
{ name: 'IONOS (Profitbricks)', value: 'ionos-objectstorage' }, { name: 'IONOS (Profitbricks)', value: 'ionos-objectstorage' },
{ name: 'Linode Object Storage', value: 'linode-objectstorage' }, { name: 'Linode Object Storage', value: 'linode-objectstorage' },
@@ -423,7 +427,7 @@ angular.module('Application').filter('markdown2html', function () {
angular.module('Application').config(['$translateProvider', function ($translateProvider) { angular.module('Application').config(['$translateProvider', function ($translateProvider) {
$translateProvider.useStaticFilesLoader({ $translateProvider.useStaticFilesLoader({
prefix: 'translation/', prefix: 'translation/',
suffix: '.json?' + '<%= revision %>' suffix: '.json'
}); });
$translateProvider.useLocalStorage(); $translateProvider.useLocalStorage();
$translateProvider.preferredLanguage('en'); $translateProvider.preferredLanguage('en');
@@ -616,21 +620,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
.error(defaultErrorHandler(callback)); .error(defaultErrorHandler(callback));
} }
function head(url, config, callback) {
if (arguments.length !== 3) {
console.error('HEAD', arguments);
throw('Wrong number of arguments');
}
config = config || {};
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + token;
return $http.head(client.apiOrigin + url, config)
.success(defaultSuccessHandler(callback))
.error(defaultErrorHandler(callback));
}
function post(url, data, config, callback) { function post(url, data, config, callback) {
if (arguments.length !== 4) { if (arguments.length !== 4) {
console.error('POST', arguments); console.error('POST', arguments);
@@ -692,7 +681,8 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
source: null, source: null,
avatarUrl: null, avatarUrl: null,
avatarType: null, avatarType: null,
hasBackgroundImage: false hasBackgroundImage: false,
notificationConfig: []
}; };
this._config = { this._config = {
consoleServerOrigin: null, consoleServerOrigin: null,
@@ -708,7 +698,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
this._installedAppsById = {}; this._installedAppsById = {};
this._appTags = []; this._appTags = [];
// window.location fallback for websocket connections which do not have relative uris // window.location fallback for websocket connections which do not have relative uris
this.apiOrigin = '<%= apiOrigin %>' || window.location.origin; this.apiOrigin = window.cloudronApiOrigin || window.location.origin;
this.avatar = ''; this.avatar = '';
this.background = ''; this.background = '';
this._availableLanguages = ['en']; this._availableLanguages = ['en'];
@@ -734,7 +724,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
// this happens mostly if the box crashes // this happens mostly if the box crashes
if (message === 'Empty message or object') { if (message === 'Empty message or object') {
message = 'Got empty response. Click to check the server logs.'; message = 'Got empty response. Click to check the server logs.';
action = action || '/frontend/logs.html?id=box'; action = action || '/logs.html?id=box';
} }
this.notify('Cloudron Error', message, true, 'error', action); this.notify('Cloudron Error', message, true, 'error', action);
@@ -744,7 +734,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
Client.prototype.initError = function (error, initFunction) { Client.prototype.initError = function (error, initFunction) {
console.error('Application startup error', error); console.error('Application startup error', error);
$timeout(initFunction, 5000); // we will try to re-init the app // $timeout(initFunction, 5000); // we will try to re-init the app
}; };
Client.prototype.clearNotifications = function () { Client.prototype.clearNotifications = function () {
@@ -824,6 +814,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
this._userInfo.avatarUrl = userInfo.avatarUrl + '?ts=' + Date.now(); // we add the timestamp to avoid caching this._userInfo.avatarUrl = userInfo.avatarUrl + '?ts=' + Date.now(); // we add the timestamp to avoid caching
this._userInfo.avatarType = userInfo.avatarType; this._userInfo.avatarType = userInfo.avatarType;
this._userInfo.hasBackgroundImage = userInfo.hasBackgroundImage; this._userInfo.hasBackgroundImage = userInfo.hasBackgroundImage;
this._userInfo.notificationConfig = userInfo.notificationConfig;
this._userInfo.isAtLeastOwner = [ ROLES.OWNER ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastOwner = [ ROLES.OWNER ].indexOf(userInfo.role) !== -1;
this._userInfo.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(userInfo.role) !== -1;
this._userInfo.isAtLeastMailManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER ].indexOf(userInfo.role) !== -1; this._userInfo.isAtLeastMailManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER ].indexOf(userInfo.role) !== -1;
@@ -835,9 +826,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
angular.copy(config, this._config); angular.copy(config, this._config);
<% if (appstore.consoleOrigin) { -%> // <% if (appstore.consoleOrigin) { -%>
this._config.consoleServerOrigin = '<%= appstore.consoleOrigin %>'; // this._config.consoleServerOrigin = '<%= appstore.consoleOrigin %>';
<% } -%> // <% } -%>
this._configListener.forEach(function (callback) { this._configListener.forEach(function (callback) {
callback(that._config); callback(that._config);
@@ -952,7 +943,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
}); });
}; };
Client.prototype.installApp = function (id, manifest, title, config, callback) { Client.prototype.installApp = function (id, manifest, config, callback) {
var data = { var data = {
appStoreId: id + '@' + manifest.version, appStoreId: id + '@' + manifest.version,
subdomain: config.subdomain, subdomain: config.subdomain,
@@ -964,10 +955,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
key: config.key, key: config.key,
sso: config.sso, sso: config.sso,
overwriteDns: config.overwriteDns, overwriteDns: config.overwriteDns,
upstreamUri: config.upstreamUri upstreamUri: config.upstreamUri,
backupId: config.backupId // when restoring from archive
}; };
post('/api/v1/apps/install', data, null, function (error, data, status) { post('/api/v1/apps', data, null, function (error, data, status) {
if (error) return callback(error); if (error) return callback(error);
if (status !== 202) return callback(new ClientError(status, data)); if (status !== 202) return callback(new ClientError(status, data));
@@ -1015,6 +1007,17 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
}); });
}; };
Client.prototype.archiveApp = function (appId, backupId, callback) {
var data = { backupId: backupId };
post('/api/v1/apps/' + appId + '/archive', data, null, function (error, data, status) {
if (error) return callback(error);
if (status !== 202) return callback(new ClientError(status, data));
callback(null);
});
};
Client.prototype.uninstallApp = function (appId, callback) { Client.prototype.uninstallApp = function (appId, callback) {
var data = {}; var data = {};
@@ -1498,6 +1501,41 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
}); });
}; };
Client.prototype.listArchives = function (callback) {
var config = {
params: {
page: 1,
per_page: 100
}
};
get('/api/v1/archives', config, function (error, data, status) {
if (error) return callback(error);
if (status !== 200) return callback(new ClientError(status, data));
callback(null, data.archives);
});
};
Client.prototype.deleteArchive = function (id, callback) {
del('/api/v1/archives/' + id, null, function (error, data, status) {
if (error) return callback(error);
if (status !== 204) return callback(new ClientError(status, data));
callback(null);
});
};
Client.prototype.unarchiveApp = function (archiveId, data, callback) {
post('/api/v1/archives/' + archiveId + '/unarchive', data, null, function (error, data, status) {
if (error) return callback(error);
if (status !== 202) return callback(new ClientError(status, data));
callback(null, data);
});
};
Client.prototype.getBackups = function (callback) { Client.prototype.getBackups = function (callback) {
var page = 1; var page = 1;
var perPage = 100; var perPage = 100;
@@ -2326,7 +2364,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
Client.prototype.updateApplink = function (id, data, callback) { Client.prototype.updateApplink = function (id, data, callback) {
post('/api/v1/applinks/' + id, data, null, function (error, data, status) { post('/api/v1/applinks/' + id, data, null, function (error, data, status) {
if (error) return callback(error); if (error) return callback(error);
if (status !== 202) return callback(new ClientError(status, data)); if (status !== 200) return callback(new ClientError(status, data));
callback(null); callback(null);
}); });
@@ -2421,6 +2459,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
}); });
}; };
Client.prototype.setNotificationConfig = function (notificationConfig, callback) {
post('/api/v1/profile/notification_config', { notificationConfig }, null, function (error, data, status) {
if (error) return callback(error);
if (status !== 204) return callback(new ClientError(status, data));
callback(null, data);
});
};
Client.prototype.setProfileEmail = function (email, password, callback) { Client.prototype.setProfileEmail = function (email, password, callback) {
post('/api/v1/profile/email', { email: email, password: password }, null, function (error, data, status) { post('/api/v1/profile/email', { email: email, password: password }, null, function (error, data, status) {
if (error) return callback(error); if (error) return callback(error);
@@ -2816,7 +2863,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
localStorage.setItem('redirectToHash', window.location.hash); localStorage.setItem('redirectToHash', window.location.hash);
// start oidc flow // start oidc flow
window.location.href = this.apiOrigin + '/openid/auth?client_id=' + ('<%= apiOrigin %>' ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html'; window.location.href = this.apiOrigin + '/openid/auth?client_id=' + (window.cloudronApiOrigin ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
}; };
Client.prototype.logout = function () { Client.prototype.logout = function () {
@@ -3118,18 +3165,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
}); });
}; };
Client.prototype.getSolrConfig = function (callback) { Client.prototype.getFtsConfig = function (callback) {
var config = {}; var config = {};
get('/api/v1/mailserver/solr_config', config, function (error, data, status) { get('/api/v1/mailserver/fts_config', config, function (error, data, status) {
if (error) return callback(error); if (error) return callback(error);
if (status !== 200) return callback(new ClientError(status, data)); if (status !== 200) return callback(new ClientError(status, data));
callback(null, data); callback(null, data);
}); });
}; };
Client.prototype.setSolrConfig = function (enabled, callback) { Client.prototype.setFtsConfig = function (state, callback) {
post('/api/v1/mailserver/solr_config', { enabled: enabled }, null, function (error, data, status) { post('/api/v1/mailserver/fts_config', { enable: state }, null, function (error, data, status) {
if (error) return callback(error); if (error) return callback(error);
if (status !== 200) return callback(new ClientError(status, data)); if (status !== 200) return callback(new ClientError(status, data));
@@ -3568,8 +3615,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
// basically the user has not setup appstore account yet // basically the user has not setup appstore account yet
if (!subscription.plan) return window.location.href = '/#/appstore'; if (!subscription.plan) return window.location.href = '/#/appstore';
if (subscription.plan.id === 'free') window.open(this.getConfig().consoleServerOrigin + '/#/subscription_setup/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank'); window.open(this.getConfig().consoleServerOrigin + '/#/cloudron/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank');
else window.open(this.getConfig().consoleServerOrigin + '/#/cloudron/' + subscription.cloudronId + '?email=' + subscription.emailEncoded, '_blank');
}; };
Client.prototype.getAppstoreAppByIdAndVersion = function (appId, version, callback) { Client.prototype.getAppstoreAppByIdAndVersion = function (appId, version, callback) {
@@ -3679,10 +3725,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
var ACTION_APP_STOP = 'app.stop'; var ACTION_APP_STOP = 'app.stop';
var ACTION_APP_RESTART = 'app.restart'; var ACTION_APP_RESTART = 'app.restart';
var ACTION_ARCHIVES_ADD = 'archives.add';
var ACTION_ARCHIVES_DEL = 'archives.del';
var ACTION_BACKUP_FINISH = 'backup.finish'; var ACTION_BACKUP_FINISH = 'backup.finish';
var ACTION_BACKUP_START = 'backup.start'; var ACTION_BACKUP_START = 'backup.start';
var ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start'; var ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start';
var ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish'; var ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish';
var ACTION_BRANDING_AVATAR = 'branding.avatar';
var ACTION_BRANDING_NAME = 'branding.name';
var ACTION_BRANDING_FOOTER = 'branding.footer';
var ACTION_CERTIFICATE_NEW = 'certificate.new'; var ACTION_CERTIFICATE_NEW = 'certificate.new';
var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew'; var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
var ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup'; var ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup';
@@ -3697,6 +3751,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
var ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure'; var ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure';
var ACTION_GROUP_ADD = 'group.add';
var ACTION_GROUP_UPDATE = 'group.update';
var ACTION_GROUP_REMOVE = 'group.remove';
var ACTION_GROUP_MEMBERSHIP = 'group.membership';
var ACTION_INSTALL_FINISH = 'cloudron.install.finish'; var ACTION_INSTALL_FINISH = 'cloudron.install.finish';
var ACTION_START = 'cloudron.start'; var ACTION_START = 'cloudron.start';
@@ -3924,6 +3983,12 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
if (!data.app) return ''; if (!data.app) return '';
return appName('', data.app, 'App') + ' was restarted'; return appName('', data.app, 'App') + ' was restarted';
case ACTION_ARCHIVES_ADD:
return 'Backup ' + data.backupId + ' added to archive';
case ACTION_ARCHIVES_DEL:
return 'Backup ' + data.backupId + ' deleted from archive';
case ACTION_BACKUP_START: case ACTION_BACKUP_START:
return 'Backup started'; return 'Backup started';
@@ -3940,6 +4005,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
case ACTION_BACKUP_CLEANUP_FINISH: case ACTION_BACKUP_CLEANUP_FINISH:
return data.errorMessage ? 'Backup cleaner errored: ' + data.errorMessage : 'Backup cleaner removed ' + (data.removedBoxBackupPaths ? data.removedBoxBackupPaths.length : '0') + ' backups'; return data.errorMessage ? 'Backup cleaner errored: ' + data.errorMessage : 'Backup cleaner removed ' + (data.removedBoxBackupPaths ? data.removedBoxBackupPaths.length : '0') + ' backups';
case ACTION_BRANDING_AVATAR:
return 'Cloudron Avatar Changed';
case ACTION_BRANDING_NAME:
return 'Cloudron Name set to ' + data.name;
case ACTION_BRANDING_FOOTER:
return 'Cloudron Footer set to ' + data.footer;
case ACTION_CERTIFICATE_NEW: case ACTION_CERTIFICATE_NEW:
return 'Certificate install for ' + data.domain + (errorMessage ? ' failed' : ' succeeded'); return 'Certificate install for ' + data.domain + (errorMessage ? ' failed' : ' succeeded');
@@ -3975,6 +4049,18 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')'; return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')';
} }
case ACTION_GROUP_ADD:
return 'Group ' + data.name + ' was added';
case ACTION_GROUP_UPDATE:
return 'Group name changed from ' + data.oldName + ' to ' + data.group.name;
case ACTION_GROUP_REMOVE:
return 'Group ' + data.group.name + ' was removed';
case ACTION_GROUP_MEMBERSHIP:
return 'Group membership of ' + data.group.name + ' changed. Now was ' + data.userIds.length + ' member(s).';
case ACTION_INSTALL_FINISH: case ACTION_INSTALL_FINISH:
return 'Cloudron version ' + data.version + ' installed'; return 'Cloudron version ' + data.version + ' installed';
@@ -1,8 +1,6 @@
'use strict'; 'use strict';
/* global angular:false, window, document, localStorage, redirectIfNeeded */ /* global $, async, angular, redirectIfNeeded */
/* global $:false */
/* global async */
/* global ERROR,ISTATES,HSTATES,RSTATES,APP_TYPES,NOTIFICATION_TYPES */ /* global ERROR,ISTATES,HSTATES,RSTATES,APP_TYPES,NOTIFICATION_TYPES */
// deal with accessToken in the query, this is passed for example on password reset and account setup upon invite // deal with accessToken in the query, this is passed for example on password reset and account setup upon invite
@@ -50,88 +48,88 @@ app.config(['$routeProvider', function ($routeProvider) {
redirectTo: '/apps' redirectTo: '/apps'
}).when('/users', { }).when('/users', {
controller: 'UsersController', controller: 'UsersController',
templateUrl: 'views/users.html?<%= revision %>' templateUrl: 'views/users.html?' + window.VITE_CACHE_ID
}).when('/usersettings', { }).when('/user-directory', {
controller: 'UserSettingsController', controller: 'UserSettingsController',
templateUrl: 'views/user-settings.html?<%= revision %>' templateUrl: 'views/user-directory.html?' + window.VITE_CACHE_ID
}).when('/app/:appId/:view?', { }).when('/app/:appId/:view?', {
controller: 'AppController', controller: 'AppController',
templateUrl: 'views/app.html?<%= revision %>' templateUrl: 'views/app.html?' + window.VITE_CACHE_ID
}).when('/appstore', { }).when('/appstore', {
controller: 'AppStoreController', controller: 'AppStoreController',
templateUrl: 'views/appstore.html?<%= revision %>' templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
}).when('/appstore/:appId', { }).when('/appstore/:appId', {
controller: 'AppStoreController', controller: 'AppStoreController',
templateUrl: 'views/appstore.html?<%= revision %>' templateUrl: 'views/appstore.html?' + window.VITE_CACHE_ID
}).when('/apps', { }).when('/apps', {
controller: 'AppsController', controller: 'AppsController',
templateUrl: 'views/apps.html?<%= revision %>' templateUrl: 'views/apps.html?' + window.VITE_CACHE_ID
}).when('/profile', { }).when('/profile', {
controller: 'ProfileController', controller: 'ProfileController',
templateUrl: 'views/profile.html?<%= revision %>' templateUrl: 'views/profile.html?' + window.VITE_CACHE_ID
}).when('/backups', { }).when('/backups', {
controller: 'BackupsController', controller: 'BackupsController',
templateUrl: 'views/backups.html?<%= revision %>' templateUrl: 'views/backups.html?' + window.VITE_CACHE_ID
}).when('/branding', { }).when('/branding', {
controller: 'BrandingController', controller: 'BrandingController',
templateUrl: 'views/branding.html?<%= revision %>' templateUrl: 'views/branding.html?' + window.VITE_CACHE_ID
}).when('/network', { }).when('/network', {
controller: 'NetworkController', controller: 'NetworkController',
templateUrl: 'views/network.html?<%= revision %>' templateUrl: 'views/network.html?' + window.VITE_CACHE_ID
}).when('/domains', { }).when('/domains', {
controller: 'DomainsController', controller: 'DomainsController',
templateUrl: 'views/domains.html?<%= revision %>' templateUrl: 'views/domains.html?' + window.VITE_CACHE_ID
}).when('/email', { }).when('/email', {
controller: 'EmailsController', controller: 'EmailsController',
templateUrl: 'views/emails.html?<%= revision %>' templateUrl: 'views/emails.html?' + window.VITE_CACHE_ID
}).when('/emails-eventlog', { }).when('/emails-eventlog', {
controller: 'EmailsEventlogController', controller: 'EmailsEventlogController',
templateUrl: 'views/emails-eventlog.html?<%= revision %>' templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
}).when('/emails-queue', { }).when('/emails-queue', {
controller: 'EmailsQueueController', controller: 'EmailsQueueController',
templateUrl: 'views/emails-queue.html?<%= revision %>' templateUrl: 'views/emails-queue.html?' + window.VITE_CACHE_ID
}).when('/email/:domain/:view?', { }).when('/email/:domain/:view?', {
controller: 'EmailController', controller: 'EmailController',
templateUrl: 'views/email.html?<%= revision %>' templateUrl: 'views/email.html?' + window.VITE_CACHE_ID
}).when('/notifications', { }).when('/notifications', {
controller: 'NotificationsController', controller: 'NotificationsController',
templateUrl: 'views/notifications.html?<%= revision %>' templateUrl: 'views/notifications.html?' + window.VITE_CACHE_ID
}).when('/oidc', { }).when('/oidc', {
redirectTo: '/usersettings' redirectTo: '/user-directory'
}).when('/settings', { }).when('/settings', {
controller: 'SettingsController', controller: 'SettingsController',
templateUrl: 'views/settings.html?<%= revision %>' templateUrl: 'views/settings.html?' + window.VITE_CACHE_ID
}).when('/eventlog', { }).when('/eventlog', {
controller: 'EventLogController', controller: 'EventLogController',
templateUrl: 'views/eventlog.html?<%= revision %>' templateUrl: 'views/eventlog.html?' + window.VITE_CACHE_ID
}).when('/support', { }).when('/support', {
controller: 'SupportController', controller: 'SupportController',
templateUrl: 'views/support.html?<%= revision %>' templateUrl: 'views/support.html?' + window.VITE_CACHE_ID
}).when('/system', { }).when('/system', {
controller: 'SystemController', controller: 'SystemController',
templateUrl: 'views/system.html?<%= revision %>' templateUrl: 'views/system.html?' + window.VITE_CACHE_ID
}).when('/services', { }).when('/services', {
controller: 'ServicesController', controller: 'ServicesController',
templateUrl: 'views/services.html?<%= revision %>' templateUrl: 'views/services.html?' + window.VITE_CACHE_ID
}).when('/volumes', { }).when('/volumes', {
controller: 'VolumesController', controller: 'VolumesController',
templateUrl: 'views/volumes.html?<%= revision %>' templateUrl: 'views/volumes.html?' + window.VITE_CACHE_ID
}).otherwise({ redirectTo: '/'}); }).otherwise({ redirectTo: '/'});
}]); }]);
app.filter('notificationTypeToColor', function () { app.filter('notificationTypeToColor', function () {
return function (n) { return function (n) {
switch (n.type) { switch (n.type) {
case NOTIFICATION_TYPES.ALERT_REBOOT: case NOTIFICATION_TYPES.REBOOT:
case NOTIFICATION_TYPES.ALERT_APP_OOM: case NOTIFICATION_TYPES.APP_OOM:
case NOTIFICATION_TYPES.ALERT_MAIL_STATUS: case NOTIFICATION_TYPES.MAIL_STATUS:
case NOTIFICATION_TYPES.ALERT_CERTIFICATE_RENEWAL_FAILED: case NOTIFICATION_TYPES.CERTIFICATE_RENEWAL_FAILED:
case NOTIFICATION_TYPES.ALERT_DISK_SPACE: case NOTIFICATION_TYPES.DISK_SPACE:
case NOTIFICATION_TYPES.ALERT_BACKUP_CONFIG: case NOTIFICATION_TYPES.BACKUP_CONFIG:
case NOTIFICATION_TYPES.ALERT_BACKUP_FAILED: case NOTIFICATION_TYPES.BACKUP_FAILED:
return '#ff4c4c'; return '#ff4c4c';
case NOTIFICATION_TYPES.ALERT_BOX_UPDATE: case NOTIFICATION_TYPES.BOX_UPDATE:
case NOTIFICATION_TYPES.ALERT_MANUAL_APP_UPDATE: case NOTIFICATION_TYPES.MANUAL_APP_UPDATE:
return '#f0ad4e'; return '#f0ad4e';
default: default:
return '#2196f3'; return '#2196f3';
@@ -673,6 +671,10 @@ app.controller('MainController', ['$scope', '$route', '$timeout', '$location', '
$scope.hideNavBarActions = $location.path() === '/logs'; $scope.hideNavBarActions = $location.path() === '/logs';
$scope.backgroundImageUrl = ''; $scope.backgroundImageUrl = '';
$scope.closeNavbar = function () {
$('.navbar-collapse').collapse('hide');
};
$scope.reboot = { $scope.reboot = {
busy: false, busy: false,
File diff suppressed because one or more lines are too long
@@ -59,7 +59,7 @@ app.filter('tr', translateFilterFactory);
app.controller('PasswordResetController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) { app.controller('PasswordResetController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) {
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own // 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.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); const search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
$scope.initialized = false; $scope.initialized = false;
$scope.mode = ''; $scope.mode = '';
@@ -72,7 +72,8 @@ app.controller('PasswordResetController', ['$scope', '$translate', '$http', func
$scope.passwordResetIdentifier = ''; $scope.passwordResetIdentifier = '';
$scope.newPassword = ''; $scope.newPassword = '';
$scope.newPasswordRepeat = ''; $scope.newPasswordRepeat = '';
var API_ORIGIN = '<%= apiOrigin %>' || window.location.origin;
const API_ORIGIN = window.cloudronApiOrigin || window.location.origin;
$scope.onPasswordReset = function () { $scope.onPasswordReset = function () {
$scope.busy = true; $scope.busy = true;
@@ -1,7 +1,7 @@
'use strict'; 'use strict';
/* global $, angular, SECRET_PLACEHOLDER, STORAGE_PROVIDERS, BACKUP_FORMATS, window, FileReader, document, redirectIfNeeded */ /* global $, angular, SECRET_PLACEHOLDER, STORAGE_PROVIDERS, BACKUP_FORMATS, redirectIfNeeded */
/* global REGIONS_S3, REGIONS_WASABI, REGIONS_DIGITALOCEAN, REGIONS_EXOSCALE, REGIONS_SCALEWAY, REGIONS_LINODE, REGIONS_OVH, REGIONS_IONOS, REGIONS_UPCLOUD, REGIONS_VULTR, REGIONS_CONTABO */ /* global REGIONS_S3, REGIONS_WASABI, REGIONS_DIGITALOCEAN, REGIONS_EXOSCALE, REGIONS_SCALEWAY, REGIONS_LINODE, REGIONS_OVH, REGIONS_IONOS, REGIONS_UPCLOUD, REGIONS_VULTR, REGIONS_CONTABO, REGIONS_HETZNER */
// create main application module // create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']); var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
@@ -77,6 +77,7 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
$scope.s3Regions = REGIONS_S3; $scope.s3Regions = REGIONS_S3;
$scope.wasabiRegions = REGIONS_WASABI; $scope.wasabiRegions = REGIONS_WASABI;
$scope.doSpacesRegions = REGIONS_DIGITALOCEAN; $scope.doSpacesRegions = REGIONS_DIGITALOCEAN;
$scope.hetznerRegions = REGIONS_HETZNER;
$scope.exoscaleSosRegions = REGIONS_EXOSCALE; $scope.exoscaleSosRegions = REGIONS_EXOSCALE;
$scope.scalewayRegions = REGIONS_SCALEWAY; $scope.scalewayRegions = REGIONS_SCALEWAY;
$scope.linodeRegions = REGIONS_LINODE; $scope.linodeRegions = REGIONS_LINODE;
@@ -92,7 +93,7 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
$scope.s3like = function (provider) { $scope.s3like = function (provider) {
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos' return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos'
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage' || provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage' || provider === 'hetzner-objectstorage'
|| provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'backblaze-b2' || provider === 'cloudflare-r2' || provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'backblaze-b2' || provider === 'cloudflare-r2'
|| provider === 'ionos-objectstorage' || provider === 'vultr-objectstorage' || provider === 'upcloud-objectstorage' || provider === 'idrive-e2' || provider === 'ionos-objectstorage' || provider === 'vultr-objectstorage' || provider === 'upcloud-objectstorage' || provider === 'idrive-e2'
|| provider === 'contabo-objectstorage'; || provider === 'contabo-objectstorage';
@@ -162,6 +163,9 @@ app.controller('RestoreController', ['$scope', 'Client', function ($scope, Clien
backupConfig.signatureVersion = 'v4'; backupConfig.signatureVersion = 'v4';
} else if (backupConfig.provider === 'digitalocean-spaces') { } else if (backupConfig.provider === 'digitalocean-spaces') {
backupConfig.region = 'us-east-1'; backupConfig.region = 'us-east-1';
} else if (backupConfig.provider === 'hetzner-objectstorage') {
backupConfig.region = 'us-east-1';
backupConfig.signatureVersion = 'v4';
} }
} else if (backupConfig.provider === 'gcs') { } else if (backupConfig.provider === 'gcs') {
backupConfig.bucket = $scope.bucket; backupConfig.bucket = $scope.bucket;
@@ -1,6 +1,6 @@
'use strict'; 'use strict';
/* global $, angular, Clipboard, ENDPOINTS_OVH, window, FileReader, document, redirectIfNeeded */ /* global $, angular, Clipboard, ENDPOINTS_OVH, redirectIfNeeded */
// create main application module // create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']); var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ui.bootstrap']);
@@ -68,6 +68,7 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
{ name: 'GoDaddy', value: 'godaddy' }, { name: 'GoDaddy', value: 'godaddy' },
{ name: 'Google Cloud DNS', value: 'gcdns' }, { name: 'Google Cloud DNS', value: 'gcdns' },
{ name: 'Hetzner', value: 'hetzner' }, { name: 'Hetzner', value: 'hetzner' },
{ name: 'INWX', value: 'inwx' },
{ name: 'Linode', value: 'linode' }, { name: 'Linode', value: 'linode' },
{ name: 'Name.com', value: 'namecom' }, { name: 'Name.com', value: 'namecom' },
{ name: 'Namecheap', value: 'namecheap' }, { name: 'Namecheap', value: 'namecheap' },
@@ -87,6 +88,7 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
gcdnsKey: { keyFileName: '', content: '' }, gcdnsKey: { keyFileName: '', content: '' },
digitalOceanToken: '', digitalOceanToken: '',
gandiApiKey: '', gandiApiKey: '',
gandiTokenType: 'PAT',
cloudflareEmail: '', cloudflareEmail: '',
cloudflareToken: '', cloudflareToken: '',
cloudflareTokenType: 'GlobalApiKey', cloudflareTokenType: 'GlobalApiKey',
@@ -97,6 +99,8 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
bunnyAccessKey: '', bunnyAccessKey: '',
dnsimpleAccessToken: '', dnsimpleAccessToken: '',
hetznerToken: '', hetznerToken: '',
inwxUsername: '',
inwxPassword: '',
vultrToken: '', vultrToken: '',
deSecToken: '', deSecToken: '',
nameComUsername: '', nameComUsername: '',
@@ -181,6 +185,7 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
config.token = $scope.dnsCredentials.digitalOceanToken; config.token = $scope.dnsCredentials.digitalOceanToken;
} else if (provider === 'gandi') { } else if (provider === 'gandi') {
config.token = $scope.dnsCredentials.gandiApiKey; config.token = $scope.dnsCredentials.gandiApiKey;
config.tokenType = $scope.dnsCredentials.gandiTokenType;
} else if (provider === 'godaddy') { } else if (provider === 'godaddy') {
config.apiKey = $scope.dnsCredentials.godaddyApiKey; config.apiKey = $scope.dnsCredentials.godaddyApiKey;
config.apiSecret = $scope.dnsCredentials.godaddyApiSecret; config.apiSecret = $scope.dnsCredentials.godaddyApiSecret;
@@ -197,6 +202,9 @@ app.controller('SetupDNSController', ['$scope', '$http', '$timeout', 'Client', f
config.accessToken = $scope.dnsCredentials.dnsimpleAccessToken; config.accessToken = $scope.dnsCredentials.dnsimpleAccessToken;
} else if (provider === 'hetzner') { } else if (provider === 'hetzner') {
config.token = $scope.dnsCredentials.hetznerToken; config.token = $scope.dnsCredentials.hetznerToken;
} else if (provider === 'inwx') {
config.username = $scope.dnsCredentials.inwxUsername;
config.password = $scope.dnsCredentials.inwxPassword;
} else if (provider === 'vultr') { } else if (provider === 'vultr') {
config.token = $scope.dnsCredentials.vultrToken; config.token = $scope.dnsCredentials.vultrToken;
} else if (provider === 'desec') { } else if (provider === 'desec') {
@@ -62,9 +62,9 @@ app.filter('tr', translateFilterFactory);
app.controller('SetupAccountController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) { app.controller('SetupAccountController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) {
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own // 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.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); const search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
var API_ORIGIN = '<%= apiOrigin %>' || window.location.origin; const API_ORIGIN = window.cloudronApiOrigin || window.location.origin;
$scope.initialized = false; $scope.initialized = false;
$scope.busy = false; $scope.busy = false;
File diff suppressed because one or more lines are too long
@@ -1,5 +1,7 @@
/* This file contains helpers which should not be part of client.js */ /* This file contains helpers which should not be part of client.js */
/* global angular */
angular.module('Application').directive('passwordReveal', function () { angular.module('Application').directive('passwordReveal', function () {
var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>'; var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>';
var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>'; var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Some files were not shown because too many files have changed in this diff Show More