231 Commits

Author SHA1 Message Date
f7d0a0ba85 0.3.0
Some checks failed
continuous-integration/drone/push Build is failing
2022-03-02 10:41:54 -06:00
83e4dfa18f Merge pull request 'Allow comments for LNURL-PAY invoices' (#65) from feature/lnurlp_memos into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #65
2022-03-02 14:13:40 +00:00
4c70600d1f Re-add description_hash
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Necessary for lnurlpay-enabled wallets
2022-03-01 13:53:22 -06:00
9903683536 Remove desc hash, always add memo to invoices
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-03-01 13:26:44 -06:00
4c51b9c966 Allow comments for LNURL-PAY invoices
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Allows senders to add a short message to payments, which will be stored
as invoice memo by LND/LndHub.
2022-03-01 11:20:23 -06:00
6790e8383d Merge pull request 'Redesign layout and navigation' (#64) from feature/new_layout into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #64
2022-02-26 15:45:12 +00:00
ed886d8182 Introduce sidebar nav components, settings nav
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-24 18:56:07 -06:00
ca940ec35d Consolidate some styles
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-24 17:24:59 -06:00
5751c0338a Nicer buttons on small screens
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-24 13:59:51 -06:00
b9ec363f36 Remove caveat from README 2022-02-24 13:59:15 -06:00
417768a30c Fix specs, markup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-23 18:27:33 -06:00
9824dcd2c6 Remove unused specs
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-23 18:17:43 -06:00
5a784b5fa6 Improve devise views
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-23 18:16:14 -06:00
f36f6866a7 Port signup to new layout
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-23 18:07:54 -06:00
1fecfe57de Fix status views 2022-02-23 17:50:16 -06:00
3165714957 Implement proper mobile navigation
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-23 14:16:51 -06:00
4ccf43cf4a Layout classes 2022-02-23 12:13:14 -06:00
c0e79918ea Fix confirm dialog missing
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-21 11:20:58 -06:00
2b00eebb73 Fix delete link, remove obsolete notice
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-21 11:19:07 -06:00
86cdb1202b Port check-email screen to new layout 2022-02-21 11:09:57 -06:00
6a469d6a75 Allow empty values for fiat conversion 2022-02-21 11:09:44 -06:00
7d66b75216 Improve notifications, fix styles not being added
All checks were successful
continuous-integration/drone/push Build is passing
Based on https://petr.codes/blog/rails/modern-rails-flash-messages/part-3/
2022-02-21 11:03:43 -06:00
8102fa1230 WIP Add notification component for flash messages
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-20 17:22:49 -06:00
835152c656 Introduce ViewComponent
All checks were successful
continuous-integration/drone/push Build is passing
https://viewcomponent.org
2022-02-20 16:53:11 -06:00
7c5bd9aa34 Improve focused field style
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-20 12:54:16 -06:00
b329b557c4 Add compact layout for content, port sign-in screens
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-20 12:48:11 -06:00
2e301c3019 Port admin to new layout
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-20 11:22:06 -06:00
4f2b35ccb9 WIP New app layout
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-19 22:46:12 -06:00
a2889705ed Merge pull request 'Fix sign out link' (#62) from bugfix/signout into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #62
2022-02-19 18:16:00 +00:00
7cb0111449 Fix sign out link
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
The correct HTML attribute to send a DELETE request would be
`data-turbo-method`, but then it still fails with JS turned off, which
is unnecessary.

fixes #61
2022-02-19 12:12:32 -06:00
773ea24c5d Merge pull request 'Switch from Webpacker to cssbundling-rails, upgrade Tailwind CSS to version 3' (#59) from dev/cssbundling into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #59
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2022-02-17 14:45:18 +00:00
cd3e4161b8 Update dev command in README
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-16 10:46:04 -06:00
5a658ce580 Update RSpec syntax/usage
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Fixes a deprecation warning
2022-02-16 10:14:32 -06:00
6e9b38f04b Fix deprecation warning from Rails
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-16 09:41:07 -06:00
a71a9dfad0 Remove unused helper specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-16 09:38:40 -06:00
1c4e444c0b Adjust bundle options in CI
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-02-16 09:25:47 -06:00
565a3c3276 Fix broken name
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:34:18 -06:00
9fdbf27a60 Use rake tasks in CI
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:29:04 -06:00
1a9b47ceee Losing the battle
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:22:57 -06:00
908809bc48 Remove bundler version requirement
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:11:34 -06:00
9636671d57 Use rspec binstub in CI
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:10:23 -06:00
51cddd94f5 Add rspec binstub 2022-02-12 15:09:56 -06:00
123e7aa2a1 Update Gemfile 2022-02-12 15:09:41 -06:00
3596955642 Don't use deprecated bundler flags 2022-02-12 15:09:19 -06:00
562b16cf89 Update Rails CI Docker image
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 15:02:20 -06:00
830c634f88 Explicitly install dev and test gems
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:55:30 -06:00
2a793e9201 Define RAILS_ENV in CI
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:53:23 -06:00
e571ed9429 Use vanilla Yarn to build CSS in CI
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:43:51 -06:00
a67f3e466b Remove bootsnap
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:41:47 -06:00
ff3013f917 Remove all remains of Webpack
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:30:31 -06:00
0fa6c1a211 Don't pin bootsnap version
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:26:14 -06:00
30b2646b85 Fix rake command
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:22:53 -06:00
f8b86b0a22 Remove obsolete gems
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 14:21:21 -06:00
b71a2fa643 Merge pull request 'Upgrade Rails to 7.0.2, use native JS bundling' (#60) from dev/upgrade_rails into dev/cssbundling
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Reviewed-on: #60
2022-02-12 20:13:42 +00:00
eda1f3999f Update validation message in spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
They seem to have shortened the default message.
2022-02-12 14:10:04 -06:00
c06e58a0fb Use new lockbox method
The old one conflicts with Rails' own new ActiveRecord encryption
feature.
2022-02-12 14:04:41 -06:00
c33637003e Upgrade to Rails 7, new JS build setup 2022-02-12 13:55:56 -06:00
836bd0a977 Build CSS bundles in CI
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 13:55:30 -06:00
8578fbdad9 Build legacy CSS via cssbundling as well
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Add vanilla Sass builds that are also handled by cssbundling-rails.
2022-02-12 13:52:45 -06:00
878eac083c Move legacy (S)CSS files to legacy folder
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 12:43:37 -06:00
05da7f5dac Bump package version
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2022-02-12 10:01:19 -06:00
87e3b1a76c Sign Drone config 2022-02-12 09:34:36 -06:00
32f02cc18a Switch from Webpacker to cssbundling-rails, upgrade Tailwind 2022-02-11 17:23:31 -06:00
1b17cfb396 Fix typo
All checks were successful
continuous-integration/drone/push Build is passing
2022-02-03 11:32:41 -06:00
e5aa5a665c Merge pull request 'Fix LNURL pay amount validation' (#58) from bugfix/fix-max-receivable-amount into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #58
2022-02-03 17:13:20 +00:00
d37b68a6e5 Fix LNURL pay amount validation
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
We allow receiving of more than 100 sats and less than 1M sats
2022-02-03 17:32:18 +01:00
56936916ff Move SVG images to public folder
All checks were successful
continuous-integration/drone/push Build is passing
Wasn't working in production
2022-01-12 19:37:12 -06:00
c93a460cff Bump style version
All checks were successful
continuous-integration/drone/push Build is passing
Triggers rebuild
2022-01-12 18:52:32 -06:00
f5ceda35c1 Merge pull request 'Add more content/help to wallet page' (#57) from feature/wallet_page_content into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #57
2022-01-13 00:48:22 +00:00
eb0439d6dc Improve Blue Wallet instructions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-01-12 18:46:14 -06:00
c3dde3506e Add more content/help to wallet page
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Lighting Address info
* Improve explanation for wallet apps, add Alby
2022-01-10 13:37:04 -06:00
f22ffe373c Merge pull request 'Fix exception during signup' (#56) from bugfix/signup_lndhub into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #56
2022-01-10 15:31:10 +00:00
bc20e89617 Fix exception during signup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-01-10 09:28:12 -06:00
0f0f296a5e Merge pull request 'Add button for copying lndhub setup code' (#55) from feature/37-copy_setup_code into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #55
2021-12-16 14:23:13 +00:00
78aea5d608 Use Tailwind classes to hide/show elements
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-12-16 15:18:37 +01:00
f1d3e3d8ec Add button for copying lndhub setup code
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
closes #37
2021-12-15 14:54:44 +01:00
2706c76890 Merge pull request 'Improve admin LDAP user index' (#53) from feature/improve_admin_ldap_page into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #53
2021-11-30 14:38:05 +00:00
17f5eb56cd Merge pull request 'Show sats instead of BTC on donation page, refactor CSS' (#54) from feature/45-sats_everywhere into master
Some checks are pending
continuous-integration/drone/push Build is running
Reviewed-on: #54
2021-11-30 14:37:45 +00:00
aa6b677b13 Merge pull request 'Improve task for generating invitations' (#52) from feature/improve_invitation_generation into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #52
2021-11-28 20:44:06 +00:00
9abdab2274 Show sats instead of BTC on donation page, refactor CSS
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Show sats instead of BTC on donation page
* Use number delimiters on both donation page and wallet page
* Refactor donation page CSS into Tailwind directives
2021-11-28 11:53:20 -06:00
dd49d1208f Remove feature list from README
All checks were successful
continuous-integration/drone/push Build is passing
Not overly useful or impressive IMO.
2021-11-28 11:16:49 -06:00
db9118cb7c Improve admin LDAP user index
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Show which domain the current list is for
* Render text links as such
2021-11-28 11:11:41 -06:00
89913ba60b Improve task for generating invitations
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Turn the argument into the target number of unused invitations for each
user, thus not generating more invitations for users who already have a
sufficient amount of unused ones.
2021-11-28 10:40:09 -06:00
8cf631fd94 Add preconfigured lockbox credentials for development
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-26 13:05:26 -06:00
d0b359039b Merge pull request 'Wrap global JS into IIFE' (#50) from bugfix/41-turbolinks into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #50
2021-11-26 19:04:34 +00:00
84cf523049 Wrap global JS into IIFE
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
fixes #41
2021-11-26 13:01:39 -06:00
a7390ba00b Merge pull request 'Fixes/improvements for lnurl-pay' (#49) from feature/lnurlp_improvements into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #49
2021-11-26 17:37:37 +00:00
67d148d117 Lower the minimum receivable via lnurlp
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-11-26 11:34:51 -06:00
83ad6f4eef Update README
All checks were successful
continuous-integration/drone/push Build is passing
2021-11-25 19:24:56 -06:00
2e31268698 Change description and success message for lnurlp
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
closes #46
2021-11-25 19:14:43 -06:00
f3b22c02ef Set correct min/max amounts for lnurlp
fixes #47
2021-11-25 19:14:17 -06:00
dbe65b4b5a Merge pull request 'Add lndhub and lockbox configs for test environment' (#48) from bugfix/lndhub_specs into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #48
2021-11-26 00:55:18 +00:00
2871fc0f53 Add lockbox credentials for test env
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-11-25 18:51:40 -06:00
968689a512 Add lndhub config to test environment
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-11-25 18:38:54 -06:00
ab29f618f4 Update README
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 11:11:36 -06:00
94975a1b30 Merge pull request 'Add Tailwind info to README' (#44) from bugfix/35-tailwind-2 into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #44
2021-11-24 17:10:26 +00:00
cd8880d9dc Add Tailwind info to README
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-24 11:03:42 -06:00
f59182b9c1 Second try, triggering asset compilation
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:48:29 -06:00
941cb4a571 Minor CSS tweak
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:45:40 -06:00
f534898d8b Try triggering asset compilation
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-24 10:42:40 -06:00
18c7c54403 Merge pull request 'Various UI improvements' (#43) from ui/misc into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #43
2021-11-24 02:41:55 +00:00
12a9d4674b Fix a couple of oversights
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 20:40:42 -06:00
1af8e068c5 Add the wallet to the dashboard as a service
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 20:09:30 -06:00
669b163814 Make the wallet balance look a bit nicer 2021-11-23 20:08:54 -06:00
46c7affd1f Add explanatory intro to invitations page 2021-11-23 20:08:33 -06:00
7ab107b689 Hide headings
The main nav entry above is enough context.
2021-11-23 20:07:57 -06:00
5aee1a4100 Merge pull request 'Fix main nav on small screens' (#42) from bugfix/38-navbar into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #42
2021-11-23 22:02:36 +00:00
1578fb9976 Fix main nav on small screens
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
closes #38
2021-11-23 16:00:32 -06:00
8e64a7cf78 Merge pull request 'Fix invoice amount for lnurlp payments' (#40) from bugfix/lnurlp_amounts into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #40
2021-11-23 21:29:33 +00:00
8b5bd66598 Fix invoice amount for lnurlp payments
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Incoming amount is msats, but we create invoices with sats.
2021-11-23 15:28:16 -06:00
ac8552362c Merge pull request 'Adjust tailwind purge config' (#39) from bugfix/35-tailwind into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #39
2021-11-23 21:27:22 +00:00
99c86c42c5 Adjust tailwind purge config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-23 15:15:10 -06:00
d0267cb760 Update README
Some checks failed
continuous-integration/drone/push Build is failing
2021-11-22 17:05:46 -06:00
25ddab9241 Merge pull request 'Add LndHub wallets' (#33) from feature/lndhub into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #33
2021-11-22 23:04:19 +00:00
bf76ac55ee Do not allow comments for lnurlp senders
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 17:03:18 -06:00
40e5c3609e Remove obsolete files
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:29:23 -06:00
1078c034ad Remove obsolete comment
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:26:05 -06:00
bfa38ad7b2 Adjust spec for new development config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:24:42 -06:00
4f20cd0d0a Add Rake task for generating wallets
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:22:53 -06:00
e2ee33a1da Configure LndHub for production
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-22 16:19:16 -06:00
8662a4c8c1 Don't overwrite existing lndhub wallet credentials
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2021-11-22 15:51:30 -06:00
dbc811b840 Add LndHub service, lnurl-pay endpoints
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
Enables the lnurl-pay payment workflow
2021-11-22 15:41:05 -06:00
884070a3cb Show available balance on wallet page
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-21 16:47:55 -06:00
3c350155de Formatting
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-11-21 15:34:24 -06:00
21c6ebc137 Fix small issue with turbolinks
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
The JS was run again, and failed to assign variables using `const` then.
2021-11-21 13:27:55 -06:00
0a1052fcb7 Add wallet page
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
With setup code to connect Blue Wallet to Kosmos account wallets
2021-11-20 16:13:43 -06:00
f94227f9f3 Create LndHub accounts 2021-11-19 20:10:36 -06:00
088961dfec Merge pull request 'Switch to shared Kosmos font(s)' (#30) from feature/webfonts_open-sans into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #30
2021-02-25 17:11:39 +00:00
31cf353d3a Load remote fonts before other stylesheets
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
They won't apply when loaded afterwards.
2021-02-25 18:04:27 +01:00
4eb40abc9c Import webfont from shared URL
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-19 15:45:22 +01:00
682c78c7c3 Move headline styles to tailwind base
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-19 15:11:53 +01:00
f9726ad9be Use Open Sans as default font for everything 2021-02-19 15:02:07 +01:00
89188f5081 Don't purge CSS in base and component layers
All checks were successful
continuous-integration/drone/push Build is passing
The production build was purging input[type=text] styles, because it
couldn't find it in the Rails templates. Change config, so it only
purges utility classes.
2021-02-10 16:37:34 +01:00
6a6ff84ff2 Merge pull request 'Add Tailwind CSS, migrate most of the styles' (#27) from feature/tailwind-css into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #27
2021-02-10 14:29:06 +00:00
b6949acc96 Style forms, migrate more styles to Tailwind
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-09 20:32:10 +01:00
814633034f WIP Add Tailwind CSS
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 02:05:31 +01:00
260dedb6cf Merge pull request 'Set up async workers/jobs via Sidekiq' (#26) from feature/sidekiq into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #26
2021-02-03 18:12:48 +00:00
656c887811 Add missing hook to spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-03 19:11:43 +01:00
7e9af716ac Make them colors pop
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-03 13:37:44 +01:00
58cc6811f9 Move XMPP contacts exchange to background job
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-03 13:16:47 +01:00
8ad85636d9 Create LDAP users asynchronously
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-02 21:16:24 +01:00
35e2c8cd30 Add Sidekiq, configure admin access to Web UI
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-02 11:44:17 +01:00
4526c941b8 Merge pull request 'Add invitations page to admin panel' (#24) from feature/admin_invitations into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #24
2021-02-01 22:53:31 +00:00
4f5ebd5330 Merge pull request 'Add cosmic background to header' (#25) from ui/kosmic_sky into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #25
2021-02-01 22:53:15 +00:00
d7e4c6f3ae Add cosmic background to header
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Going back to space, where we belong.
2021-02-01 23:10:54 +01:00
14caefe2d1 Replace yellow menu highlight with blue color 2021-02-01 22:49:42 +01:00
0110f27ada Add invitation stats
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Show some stats about unused invitations and active inviters
2021-02-01 22:35:30 +01:00
dc7cf107c2 New admin page for invitations 2021-02-01 21:53:18 +01:00
4fbfaadb44 Merge pull request 'Various UI improvements' (#23) from ux/various_design_changes into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #23
2021-02-01 18:33:06 +00:00
a01cb9ae21 Adjust site header in admin, signup layouts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-02-01 18:58:34 +01:00
698e4381c2 Improve table styles
All checks were successful
continuous-integration/drone/push Build is passing
* Nicer table headers
* Hide invitation IDs on small screens
2021-02-01 18:53:48 +01:00
8997349186 Move password change to new Security tab
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-01 18:39:51 +01:00
92bfc33bf0 Remove bottom border from last section on page 2021-02-01 18:24:01 +01:00
c6eb21faad Change site name to "Account", add comet icon
All checks were successful
continuous-integration/drone/push Build is passing
... and remove the "beta" tag.
2021-02-01 18:17:26 +01:00
2d9bc90b16 Merge pull request 'Use new .local domain for Postgres in production' (#22) from chore/postgres_hostname into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #22
2021-01-23 14:04:45 +00:00
a0c579e319 Use new .local domain for Postgres in production
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-23 15:03:16 +01:00
f289ee9365 Switch menu items
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 11:09:04 +01:00
46a7345ce9 Merge pull request 'Add main navigation bar' (#20) from feature/main_nav into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #20
2020-12-29 10:04:42 +00:00
e12d02a988 Fix spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Content changed
2020-12-29 11:02:24 +01:00
5e8618f25a Merge pull request 'Add admin layout with admin navigation' (#21) from feature/admin_layout into feature/main_nav
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
Reviewed-on: #21
2020-12-29 09:58:57 +00:00
2bdf08a523 Add admin layout with admin navigation
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
And remove the hacky link list from the dashboard.
2020-12-28 09:32:04 +01:00
9ddd36c414 Add missing section markup
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-27 14:21:16 +01:00
9372ea7343 Add small-screen layout for main navigation 2020-12-27 14:14:53 +01:00
c62ce00184 Add main navigation bar
Make donations and invitations accessible to everyone
2020-12-27 14:03:40 +01:00
4d8cd740ba Argh
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 17:15:46 +01:00
9858572a2f Remove useless bundler version requirement
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 17:03:14 +01:00
51edf55ae9 Use zerotier for connecting to postgres
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 12:24:18 +01:00
75485ce8e9 Merge pull request 'Update postgres master host' (#19) from chore/update_postgres_host into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #19
2020-12-22 10:42:51 +00:00
fcbfcc4007 Update postgres master host
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-22 11:41:40 +01:00
cdcb7b3aef Update README
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-21 15:49:58 +01:00
bcf5172956 Merge pull request 'Add basic donation records' (#18) from feature/donation_records into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #18
2020-12-21 14:46:50 +00:00
26c6c5a3b2 Nullify donation owners when related record destroyed
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-21 13:59:46 +01:00
4a65573934 Format numbers on admin donations page
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
And fix the wrong unit display in the user donations list.
2020-12-19 14:59:16 +01:00
5e2d5c3b28 Add paid_at date to donations
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-19 13:28:47 +01:00
2f70bae523 Format and style user donations 2020-12-19 13:16:04 +01:00
40f3e8327a Basic donation records
Adds donation model/table and basic manual management in the admin
panel, as well as basic listing of users' own donations.
2020-12-17 21:56:51 +01:00
f3d6e29e4e Remove time from used invitations list
Date is enough.
2020-12-17 17:02:30 +01:00
8903ae2624 Merge pull request 'Fix XMPP API POST request' (#17) from bugfix/faraday_post into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #17
2020-12-13 13:17:57 +00:00
26e9073674 Fix XMPP API POST request
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Faraday does not turn hashes into JSON by itself apparently.
2020-12-13 14:07:25 +01:00
73a89c2601 Merge pull request 'Add missing port number to ejabberd API base URL' (#16) from bugfix/ejabberd_http_port into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #16
2020-12-13 12:57:34 +00:00
7d4dee17b7 Add missing port number to ejabberd API base URL
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-13 13:54:33 +01:00
602ca6ee94 Merge pull request 'Exchange XMPP contacts when invitee signs up' (#13) from feature/automatic_xmpp_roster into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #13
2020-12-09 20:52:14 +00:00
69fc1ca57e Add production dotenv config
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-08 20:34:13 +01:00
ee72a32c7e Exchange XMPP contacts when invitee signs up
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-08 19:16:08 +01:00
8a0d89ef60 Add webmock gem 2020-12-08 18:16:41 +01:00
54af949c7d Add faraday for HTTP requests 2020-12-08 18:16:41 +01:00
6dac732a7f Move invitation invalidation to service 2020-12-08 17:52:53 +01:00
e8c1a6066a Move user db creation to service 2020-12-08 17:39:54 +01:00
44fadb12d6 Merge pull request 'Update link to Chat service' (#11) from chore/update_service_link into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #11
2020-12-04 15:18:49 +00:00
533452469b Update link to Chat service
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
The wiki page has been moved and improved for new users.
2020-12-04 16:15:17 +01:00
efe168b205 Merge pull request 'Sign up for new account via invitation' (#9) from feature/signup_from_invite into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #9
2020-12-04 12:30:28 +00:00
5b6d6bbd00 Explain ApplicationService
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-04 13:29:07 +01:00
458b585cdb Check off invitation signup feature in README
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-03 15:14:13 +01:00
f651289410 Add mailer host config for test
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
So Devise can build URLs in confirmation emails
2020-12-03 14:59:21 +01:00
7ca91cf882 Don't run caching steps on CI when not master or PR
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2020-12-03 14:56:11 +01:00
022094ce51 Add feature spec for whole signup process
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2020-12-03 14:50:02 +01:00
2a2b0a90dc Validate email address properly 2020-12-03 14:49:37 +01:00
e44535daee Don't use deprecated method 2020-12-03 14:49:16 +01:00
c8ccb418b2 Add indexes for invitations table 2020-12-03 14:49:02 +01:00
a792d66c90 Show unused invitations list 2020-12-03 14:48:43 +01:00
f7e48ad3a6 Accept non-existing terms
Legal how does it work
2020-12-03 14:47:57 +01:00
8a7d809b92 Add scopes for invitations 2020-12-03 14:04:58 +01:00
b8e75c7c4a Re-order services on dashboard
All checks were successful
continuous-integration/drone/push Build is passing
Docs and forums are more important for most users than Gitea and Drone.
2020-12-03 13:50:07 +01:00
7a58babd8e Remove obsolete argument
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-03 13:48:23 +01:00
ba31ed559a Improve wording
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-03 00:53:43 +01:00
9cebfd3f58 Signup steps with validation 2020-12-03 00:53:25 +01:00
7aadb5cb51 Require valid invitation to start sign-up process
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-02 19:20:01 +01:00
69b99711e5 Remove fixtures, configure factory generation
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-02 15:40:41 +01:00
e5fe843814 Add task for generating invitations
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-02 15:23:18 +01:00
d7fbda0855 Add basic invitations 2020-12-02 15:22:58 +01:00
18df8fe449 Add account creation service 2020-11-29 17:31:08 +01:00
fd2ebc4ad3 Merge pull request 'Cache dependencies on CI' (#6) from chore/cache_bundle_on_ci into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #6
2020-11-26 15:44:19 +00:00
c86480e72b Cache dependencies on CI
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
https://florian-latifi.at/2018/02/21/using-drone-to-build-and-deploy-a-jekyll-site/
https://git.florian-latifi.at/flortsch/jekyll-blog/src/branch/master/.drone.yml
2020-11-21 23:17:12 +01:00
5933c387b6 Set correct email address for devise
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-21 17:17:33 +01:00
e855e3cf61 Merge pull request 'Gracefully fail config build when master secret not available' (#5) from bugfix/config_without_master_key into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
2020-11-21 15:28:55 +00:00
91c3f8da2d Gracefully fail config build when master secret not available
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
When the credentials are not accessible, e.g. on CI, parsing the
database config fails otherwise.
2020-11-21 16:22:11 +01:00
8bd69b2adb Add missing postgres gem
Some checks failed
continuous-integration/drone/push Build is failing
Needed in production
2020-11-21 14:12:09 +01:00
8493dfcf9b Add missing string quotes
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-21 14:07:01 +01:00
6550a8b4f8 Merge pull request 'Add production config and credentials' (#4) from chore/production_prep into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #4
2020-11-21 13:03:16 +00:00
fa8650b43a Improve email config
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
Add specific auth options, specify sender address
2020-11-20 22:47:35 +01:00
8a938831e9 Add production config and credentials
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2020-11-20 22:43:47 +01:00
151bd58cb4 Speed up bundle-install on CI
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-19 19:04:24 +01:00
98b5af54ea Add Drone CI to service grid
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-19 17:28:44 +01:00
61b722e868 Merge pull request 'Configure CI builds' (#3) from dev/configure_drone_ci into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #3
2020-11-19 15:40:46 +00:00
0eab4f03ac Remove Ruby version lock from Gemfile
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-11-19 14:29:16 +01:00
c96a84d1a1 Add initial Drone CI config 2020-11-19 14:29:07 +01:00
86cd5824e6 Document current and planned features
Only rough priorities for planned ones, of course. Not at all complete.
2020-11-18 16:03:21 +01:00
d420ef9eac Link to license info page in README 2020-11-18 15:38:36 +01:00
a7a3974ef3 Add AGPL license 2020-11-18 15:32:27 +01:00
cd59215b69 Update README 2020-11-18 15:28:17 +01:00
0b125cf323 Introduce media queries and small screen adjustments 2020-11-18 15:13:45 +01:00
458 changed files with 5030 additions and 8146 deletions

56
.drone.yml Normal file
View File

@@ -0,0 +1,56 @@
---
kind: pipeline
type: docker
name: CI build
steps:
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./vendor
when:
branch:
- master
- name: rspec
image: guildeducation/rails:2.7.2-12.22.0
environment:
RAILS_ENV: test
commands:
- bundle config unset deployment
- bundle config set cache_all 'true'
- bundle config set cache_path 'vendor/cache'
- bundle config set with 'development test'
- bundle install --jobs=3 --retry=3
- yarn install
- rake css:build
- rake spec
when:
branch:
- master
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./vendor
when:
branch:
- master
volumes:
- name: cache
host:
path: /var/lib/drone/tmp
---
kind: signature
hmac: f9a8cf97f6596625721365f6238f6f298aa5a7a4de10c3fb61c57202ae9d1ee1
...

View File

@@ -1,8 +1,3 @@
LDAP_HOST=192.168.33.10
LDAP_PORT=389
#
# Production LDAP server:
#
# LDAP_HOST=ldap.kosmos.org
# LDAP_PORT=636
# LDAP_USE_TLS=true
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

3
.env.production Normal file
View File

@@ -0,0 +1,3 @@
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
LNDHUB_API_URL='http://10.1.1.163:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

3
.env.test Normal file
View File

@@ -0,0 +1,3 @@
EJABBERD_API_URL='http://xmpp.example.com/api'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

6
.gitignore vendored
View File

@@ -39,3 +39,9 @@ yarn-debug.log*
# Ignore local dotenv config file
.env
# Ignore redis dumps from sidekiq
dump.rdb
/app/assets/builds/*
!/app/assets/builds/.keep

View File

@@ -1 +1 @@
2.6.1
2.7.2

56
Gemfile
View File

@@ -1,20 +1,22 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.4'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rails', '~> 7.0.2'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# View components
gem "view_component"
# Separate dependency since Rails 7.0
gem 'sprockets-rails'
# Allows custom JS build tasks to integrate with the asset pipeline
gem 'cssbundling-rails'
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
@@ -22,38 +24,52 @@ gem 'jbuilder', '~> 2.7'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
# Configuration
gem 'dotenv-rails'
gem 'dotenv-rails', groups: [:development, :test]
# Security
gem 'lockbox'
# Authentication
gem 'warden'
gem 'devise'
gem 'devise_ldap_authenticatable'
gem 'net-ldap'
# Utilities
gem "rqrcode", "~> 2.0"
# HTTP requests
gem 'faraday'
# Background/scheduled jobs
gem 'sidekiq'
gem 'sidekiq-scheduler'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rspec-rails'
end
group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'letter_opener'
gem 'letter_opener_web'
end
group :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'capybara'
gem 'database_cleaner'
gem 'webmock'
end
group :production do
# Use postgresql as the database for Active Record
gem 'pg', '~> 1.2.3'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

View File

@@ -1,81 +1,100 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (6.0.3.4)
actionpack (= 6.0.3.4)
actioncable (7.0.2.2)
actionpack (= 7.0.2.2)
activesupport (= 7.0.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actionmailbox (7.0.2.2)
actionpack (= 7.0.2.2)
activejob (= 7.0.2.2)
activerecord (= 7.0.2.2)
activestorage (= 7.0.2.2)
activesupport (= 7.0.2.2)
mail (>= 2.7.1)
actionmailer (6.0.3.4)
actionpack (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
net-imap
net-pop
net-smtp
actionmailer (7.0.2.2)
actionpack (= 7.0.2.2)
actionview (= 7.0.2.2)
activejob (= 7.0.2.2)
activesupport (= 7.0.2.2)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (6.0.3.4)
actionview (= 6.0.3.4)
activesupport (= 6.0.3.4)
rack (~> 2.0, >= 2.0.8)
actionpack (7.0.2.2)
actionview (= 7.0.2.2)
activesupport (= 7.0.2.2)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4)
actionpack (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actiontext (7.0.2.2)
actionpack (= 7.0.2.2)
activerecord (= 7.0.2.2)
activestorage (= 7.0.2.2)
activesupport (= 7.0.2.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (6.0.3.4)
activesupport (= 6.0.3.4)
actionview (7.0.2.2)
activesupport (= 7.0.2.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.4)
activesupport (= 6.0.3.4)
activejob (7.0.2.2)
activesupport (= 7.0.2.2)
globalid (>= 0.3.6)
activemodel (6.0.3.4)
activesupport (= 6.0.3.4)
activerecord (6.0.3.4)
activemodel (= 6.0.3.4)
activesupport (= 6.0.3.4)
activestorage (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
marcel (~> 0.3.1)
activesupport (6.0.3.4)
activemodel (7.0.2.2)
activesupport (= 7.0.2.2)
activerecord (7.0.2.2)
activemodel (= 7.0.2.2)
activesupport (= 7.0.2.2)
activestorage (7.0.2.2)
actionpack (= 7.0.2.2)
activejob (= 7.0.2.2)
activerecord (= 7.0.2.2)
activesupport (= 7.0.2.2)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.7.0)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.5.0)
msgpack (~> 1.0)
builder (3.2.4)
byebug (11.1.3)
capybara (3.33.0)
capybara (3.36.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (~> 1.5)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
concurrent-ruby (1.1.7)
chunky_png (1.4.0)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
crack (0.4.5)
rexml
crass (1.0.6)
database_cleaner (1.8.5)
devise (4.7.3)
cssbundling-rails (1.0.0)
railties (>= 6.0.0)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.1)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
devise (4.8.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -84,192 +103,241 @@ GEM
devise_ldap_authenticatable (0.8.7)
devise (>= 3.4.1)
net-ldap (>= 0.16.0)
diff-lcs (1.4.4)
dotenv (2.7.2)
dotenv-rails (2.7.2)
dotenv (= 2.7.2)
railties (>= 3.2, < 6.1)
erubi (1.9.0)
factory_bot (6.1.0)
diff-lcs (1.5.0)
digest (3.1.0)
dotenv (2.7.6)
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
e2mmap (0.1.0)
erubi (1.10.0)
et-orbi (1.2.6)
tzinfo
factory_bot (6.2.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.1.0)
factory_bot (~> 6.1.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
ffi (1.13.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
i18n (1.8.5)
faraday (2.2.0)
faraday-net_http (~> 2.0)
ruby2_keywords (>= 0.0.4)
faraday-net_http (2.0.1)
ffi (1.15.5)
fugit (1.5.2)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.4)
globalid (1.0.0)
activesupport (>= 5.0)
hashdiff (1.0.1)
i18n (1.9.1)
concurrent-ruby (~> 1.0)
jbuilder (2.10.1)
importmap-rails (1.0.2)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
io-wait (0.2.1)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
launchy (2.4.3)
addressable (~> 2.3)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (1.3.4)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
listen (3.2.1)
letter_opener_web (2.0.0)
actionmailer (>= 5.2)
letter_opener (~> 1.7)
railties (>= 5.2)
rexml
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.7.0)
lockbox (0.6.8)
loofah (2.14.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0)
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.2)
msgpack (1.3.3)
net-ldap (0.16.3)
nio4r (2.5.4)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
mini_mime (1.1.2)
minitest (5.15.0)
net-imap (0.2.3)
digest
net-protocol
strscan
net-ldap (0.17.0)
net-pop (0.1.1)
digest
net-protocol
timeout
net-protocol (0.1.2)
io-wait
timeout
net-smtp (0.3.1)
digest
net-protocol
timeout
nio4r (2.5.8)
nokogiri (1.13.1-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
pg (1.2.3)
public_suffix (4.0.6)
puma (4.3.6)
puma (4.3.11)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.0)
rack (2.2.3)
rack-proxy (0.6.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.3.4)
actioncable (= 6.0.3.4)
actionmailbox (= 6.0.3.4)
actionmailer (= 6.0.3.4)
actionpack (= 6.0.3.4)
actiontext (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
activemodel (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
bundler (>= 1.3.0)
railties (= 6.0.3.4)
sprockets-rails (>= 2.0.0)
rails (7.0.2.2)
actioncable (= 7.0.2.2)
actionmailbox (= 7.0.2.2)
actionmailer (= 7.0.2.2)
actionpack (= 7.0.2.2)
actiontext (= 7.0.2.2)
actionview (= 7.0.2.2)
activejob (= 7.0.2.2)
activemodel (= 7.0.2.2)
activerecord (= 7.0.2.2)
activestorage (= 7.0.2.2)
activesupport (= 7.0.2.2)
bundler (>= 1.15.0)
railties (= 7.0.2.2)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
railties (6.0.3.4)
actionpack (= 6.0.3.4)
activesupport (= 6.0.3.4)
railties (7.0.2.2)
actionpack (= 7.0.2.2)
activesupport (= 7.0.2.2)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
rake (13.0.1)
rb-fsevent (0.10.4)
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rake (13.0.6)
rb-fsevent (0.11.1)
rb-inotify (0.10.1)
ffi (~> 1.0)
regexp_parser (1.8.2)
redis (4.6.0)
regexp_parser (2.2.1)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rspec-core (3.10.0)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.0)
rexml (3.2.5)
rqrcode (2.1.1)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-mocks (3.10.0)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (4.0.1)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-core (~> 3.9)
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.10.0)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
spring (2.1.1)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
rspec-support (~> 3.11.0)
rspec-rails (5.1.0)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.11.0)
ruby2_keywords (0.0.5)
rufus-scheduler (3.8.1)
fugit (~> 1.1, >= 1.1.6)
sidekiq (6.4.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-scheduler (3.1.1)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
sprockets (4.0.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.4.2)
thor (1.0.1)
thread_safe (0.3.6)
stimulus-rails (1.0.2)
railties (>= 6.0.0)
strscan (3.0.1)
thor (1.2.1)
thwait (0.2.0)
e2mmap
tilt (2.0.10)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (1.2.7)
thread_safe (~> 0.1)
timeout (0.2.0)
turbo-rails (1.0.1)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
view_component (2.49.0)
activesupport (>= 5.0.0, < 8.0)
method_source (~> 1.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.1.0)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webpacker (4.3.0)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
websocket-driver (0.7.3)
webmock (3.14.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.4.1)
zeitwerk (2.5.4)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
bootsnap (>= 1.4.2)
byebug
capybara
cssbundling-rails
database_cleaner
devise
devise_ldap_authenticatable
dotenv-rails
factory_bot_rails
faraday
importmap-rails
jbuilder (~> 2.7)
letter_opener
letter_opener_web
listen (~> 3.2)
lockbox
net-ldap
pg (~> 1.2.3)
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.4)
rails (~> 7.0.2)
rqrcode (~> 2.0)
rspec-rails
sass-rails (>= 6)
spring
spring-watcher-listen (~> 2.0.0)
sidekiq
sidekiq-scheduler
sprockets-rails
sqlite3 (~> 1.4)
turbolinks (~> 5)
stimulus-rails
turbo-rails
tzinfo-data
view_component
warden
web-console (>= 3.3.0)
webpacker (~> 4.0)
RUBY VERSION
ruby 2.6.1p33
webmock
BUNDLED WITH
2.0.2
2.3.7

661
LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2020 Kosmos Contributors <https://kosmos.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

2
Procfile.dev Normal file
View File

@@ -0,0 +1,2 @@
web: bin/rails server -p 3000
css: yarn build:css --watch

View File

@@ -1,28 +1,65 @@
# README
[![Build Status](https://drone.kosmos.org/api/badges/kosmos/akkounts/status.svg)](https://drone.kosmos.org/kosmos/akkounts)
This README would normally document whatever steps are necessary to get the
application up and running.
# Akkounts
Things you may want to cover:
This app allows Kosmos/LDAP users to manage their accounts, including
credentials, invites, donations, etc..
* Ruby version
## Development
* System dependencies
### Rails app
* Configuration
Installing dependencies:
* Database creation
bundle install
yarn install
* Database initialization
Setting up local database (SQLite):
* How to run the test suite
bundle exec rails db:create
bundle exec rails db:migrate
* Services (job queues, cache servers, search engines, etc.)
Running the dev server and auto-building CSS files on change:
* Deployment instructions
bin/dev
* ...
Running the background workers (requires Redis):
bundle exec sidekiq -C config/sidekiq.yml
Running all specs:
bundle exec rspec
### LDAP server
TODO make it easy to run a local Kosmos LDAP server for development, without
manual LDIF imports etc. (or provide a staging instance)
## Documentation
* [Ruby on Rails](https://guides.rubyonrails.org/)
* [Sass](https://sass-lang.com/documentation)
### Front-end
* [Tailwind CSS](https://tailwindcss.com/)
### Testing
* [RSpec](https://rspec.info/documentation/)
* [Capybara](https://rubydoc.info/github/teamcapybara/capybara/master)
### LDAP / Auth
* [devise_ldap_authenticatable](https://github.com/cschiewek/devise_ldap_authenticatable)
* [net/ldap](https://www.rubydoc.info/gems/net-ldap/Net/LDAP)
### Asynchronous jobs/workers
* [Sidekiq](https://github.com/mperham/sidekiq/wiki/)
* [ActiveJob](https://github.com/mperham/sidekiq/wiki/Active-Job)
## License
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)

0
app/assets/builds/.keep Normal file
View File

View File

@@ -1,2 +1,3 @@
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../builds

View File

@@ -1,13 +0,0 @@
html, body, h1, h2, h3, h4, h5, h6, p, pre, a, dl, dt, dd, ol, ul, li {
font-size: 100%;
vertical-align: baseline;
background: transparent;
box-sizing: border-box;
overflow: visible;
margin: 0;
padding: 0;
}
body {
line-height: 1;
}

View File

@@ -1,15 +0,0 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/

View File

@@ -0,0 +1,9 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "components/base";
@import "components/buttons";
@import "components/forms";
@import "components/links";
@import "components/notifications";

View File

@@ -0,0 +1,29 @@
@layer base {
body {
@apply leading-none
}
/* h1, h2, h3 { */
/* @apply font-light; */
/* } */
h1 {
@apply text-3xl uppercase;
}
h2 {
@apply text-2xl mb-8;
}
h3 {
@apply text-xl mb-6;
}
main section {
@apply pt-8 sm:pt-12;
}
main section:first-of-type {
@apply pt-0;
}
}

View File

@@ -0,0 +1,31 @@
@layer components {
.btn {
@apply font-semibold rounded-md leading-none cursor-pointer text-center
transition-colors duration-75 focus:outline-none focus:ring-4;
}
.btn-md {
@apply btn;
@apply py-2.5 px-5 shadow-md;
}
.btn-sm {
@apply btn;
@apply py-1 px-2 text-sm shadow-sm;
}
.btn-gray {
@apply bg-gray-100 hover:bg-gray-200
focus:ring-gray-300 focus:ring-opacity-75;
}
.btn-blue {
@apply bg-blue-500 hover:bg-blue-600 text-white
focus:ring-blue-400 focus:ring-opacity-75;
}
.btn-red {
@apply bg-red-600 hover:bg-red-700 text-white
focus:ring-red-500 focus:ring-opacity-75;
}
}

View File

@@ -0,0 +1,16 @@
@layer components {
input[type=text], input[type=email], input[type=password],
input[type=number], select {
@apply mt-1 rounded-md bg-gray-100 focus:bg-white
border-transparent focus:border-transparent focus:ring-2
focus:ring-blue-600 focus:ring-opacity-75;
}
.field_with_errors {
@apply inline-block;
}
.error-msg {
@apply text-red-700;
}
}

View File

@@ -0,0 +1,14 @@
@layer components {
.ks-text-link {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
.devise-links {
a {
@apply ks-text-link;
}
}
}

View File

@@ -0,0 +1,39 @@
@layer components {
@keyframes notification-countdown {
from { width: 100%; }
to { width: 0; }
}
.notification-enter {
@apply transform ease-out duration-300 transition;
}
.notification-enter-from {
@apply translate-y-2 opacity-0;
}
.notification-enter-to {
@apply translate-y-0 opacity-100;
}
@screen sm {
.notification-enter-from {
@apply translate-y-0 translate-x-2;
}
.notification-enter-to {
@apply translate-x-0;
}
}
.notification-leave {
@apply transition ease-in duration-100;
}
.notification-leave-from {
@apply opacity-100;
}
.notification-leave-to {
@apply opacity-0;
}
}

View File

@@ -1,20 +0,0 @@
@font-face {
font-family: 'Raleway';
src: url('/fonts/raleway-light.woff') format('woff2');
font-weight: 300;
font-style: normal;
}
body {
font-family: "Open Sans", Helvetica, Arial, sans-serif;
font-weight: 400;
}
h1, h2, h3 {
font-family: Raleway, inherit;
font-weight: 300;
}
h1 {
text-transform: uppercase;
}

View File

@@ -1,146 +0,0 @@
$content-width: 800px;
$content-max-width: 100%;
body {
}
#wrapper {
width: 100%;
text-align: center;
> header {
margin: 0 auto;
padding: 4rem 0;
text-align: center;
background: #0d4f99;
background: linear-gradient(35deg, #8955a0 0, #0d4f99 100%);
h1 {
font-size: 1.8rem;
color: #fff;
span.project-name {
display: none;
// font-size: .5em;
// text-transform: none;
// vertical-align: super;
}
span.beta {
font-size: .5em;
font-style: italic;
text-transform: none;
vertical-align: super;
}
span.bolt {
color: #ffd000;
}
}
p.current-user {
margin-top: 2rem;
color: rgba(255,255,255,0.6);
strong {
font-weight: 400;
color: #fff;
// color: #ffd000;
// color: #ccff40;
}
}
a {
color: rgba(255,255,255,0.6);
transition: color 0.1s linear;
&:hover, &:active {
color: #fff;
}
}
}
}
.flash-msg {
width: 100%;
text-align: center;
padding: 2rem 0;
&.notice {
background: #efffc4;
}
&.alert {
background: #fff4c2;
}
}
main {
width: $content-width;
max-width: $content-max-width;
margin: 4rem auto;
text-align: left;
h2, h3 {
margin-bottom: 1.5em;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
p {
line-height: 1.5rem;
margin-bottom: 1rem;
&.notice {
text-align: center;
}
}
ul {
margin-bottom: 1.5rem;
li {
line-height: 1.5rem;
}
}
th, td {
line-height: 1.5rem;
padding-right: 1rem;
}
section {
border-bottom: 1px dotted #ccc;
padding-bottom: 4rem;
margin-bottom: 4rem;
}
}
.grid {
display: grid;
&.services {
grid-template-columns: 1fr 1fr 1fr;
grid-row-gap: 1rem;
grid-column-gap: 2rem;
margin-top: 3rem;
h3 {
margin-bottom: 1rem;
}
.grid-item {
p {
color: #888;
font-size: 0.85rem;
}
}
}
}

View File

@@ -0,0 +1,2 @@
@import "legacy/layout";
@import "legacy/main_nav";

View File

@@ -0,0 +1,118 @@
@import "variables";
@import "mediaqueries";
body {
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
url('/img/bg-1.jpg');
background-size: cover;
background-attachment: fixed;
}
body#admin {
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
url('/img/bg-1.jpg');
background-size: cover;
background-attachment: fixed;
}
.ks-site-icon {
svg {
display: inline-block;
height: 1.875rem;
vertical-align: top;
width: auto;
}
}
#wrapper {
width: 100%;
text-align: center;
> header {
margin: 0 auto;
padding: 4rem 0;
text-align: center;
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
url('/img/bg-1.jpg');
background-size: cover;
@include media-max(small) {
padding: 3rem 0;
}
h1 {
color: #fff;
span.project-name {
display: none;
}
}
p.current-user {
color: rgba(255,255,255,0.6);
@include media-max(small) {
font-size: 0.85rem;
}
}
a {
color: rgba(255,255,255,0.6);
transition: color 0.1s linear;
&:hover, &:active {
color: #fff;
}
}
}
}
main {
p {
line-height: 1.5rem;
margin-bottom: 1rem;
&.notice {
text-align: center;
}
}
ul {
margin-bottom: 1.5rem;
li {
line-height: 1.5rem;
}
}
table {
width: 100%;
th {
color: $text-color-discreet;
font-weight: normal;
text-transform: uppercase;
font-size: 0.85rem;
padding-bottom: 0.825rem;
}
td {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
}
}
.grid {
display: grid;
&.services {
grid-template-columns: 1fr 1fr 1fr;
grid-row-gap: 1rem;
grid-column-gap: 2rem;
@include media-max(small) {
grid-template-columns: 1fr;
}
}
}

View File

@@ -0,0 +1,54 @@
@import "variables";
@import "mediaqueries";
#main-nav {
width: 100%;
text-align: center;
background-color: #efefef;
.wrapper {
width: $content-width;
max-width: $content-max-width;
margin: 0 auto;
}
ul {
@include media-max(large) {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
li {
@include media-min(large) {
display: inline;
}
@include media-max(large) {
display: block;
}
a {
display: inline-block;
padding: 1.5rem 2rem;
text-decoration: none;
color: $text-color-discreet;
@include media-max(large) {
display: block;
text-align: center;
padding-left: 0;
padding-right: 0;
}
@include media-max(small) {
font-size: 0.85rem;
}
&.active {
color: $text-color-body;
border-bottom: 2px solid #4ea2df;
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
$breakpoints-max: (
small: 600px,
medium: 960px,
large: 1280px
);
$breakpoints-min: (
small: 601px,
medium: 961px,
large: 1281px
);
@mixin media-max($screen-size) {
@if map-has-key($breakpoints-max, $screen-size) {
@media (max-width: map-get($breakpoints-max, $screen-size)) {
@content;
}
} @else {
// Debugging
@warn "'#{$screen-size}' has not been declared as a breakpoint."
}
}
@mixin media-min($screen-size) {
@if map-has-key($breakpoints-min, $screen-size) {
@media (min-width: map-get($breakpoints-min, $screen-size)) {
@content;
}
} @else {
// Debugging
@warn "'#{$screen-size}' has not been declared as a breakpoint."
}
}

View File

@@ -0,0 +1,13 @@
$content-width: 800px;
$content-max-width: 100%;
$text-color-body: #222;
$text-color-discreet: #888;
$background-color-notice: #efffc4;
$background-color-alert: #fff4c2;
$color-blue: #0d4f99;
$color-purple: #8955a0;
$color-red-bright: #c00;
$color-red-dark: #990c0e;

View File

@@ -0,0 +1,7 @@
<header class="py-10">
<div class="max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold text-white text-center">
<%= @title %>
</h1>
</div>
</header>

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class HeaderCompactComponent < ViewComponent::Base
def initialize(title:)
@title = title
end
end

View File

@@ -0,0 +1,7 @@
<header class="py-10">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold text-white">
<%= @title %>
</h1>
</div>
</header>

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class HeaderComponent < ViewComponent::Base
def initialize(title:)
@title = title
end
end

View File

@@ -0,0 +1,5 @@
<main class="w-full max-w-xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %>
</div>
</main>

View File

@@ -0,0 +1,5 @@
# frozen_string_literal: true
class MainCompactComponent < ViewComponent::Base
end

View File

@@ -0,0 +1,5 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %>
</div>
</main>

View File

@@ -0,0 +1,5 @@
# frozen_string_literal: true
class MainSimpleComponent < ViewComponent::Base
end

View File

@@ -0,0 +1,15 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow">
<div class="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside class="py-6 sm:py-8 lg:col-span-3">
<nav class="space-y-1">
<%= render partial: @sidenav_partial %>
</nav>
</aside>
<div class="lg:col-span-9 px-6 sm:px-12 py-8 sm:pt-10 sm:pb-12">
<%= content %>
</div>
</div>
</div>
</main>

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class MainWithSidenavComponent < ViewComponent::Base
def initialize(sidenav_partial:)
@sidenav_partial = sidenav_partial
end
end

View File

@@ -0,0 +1,49 @@
<div class="flash-msg <%= @type %> hidden max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto mt-4"
data-turbo="false"
data-notification-action-url="<%= @data.dig(:action, :url) %>"
data-notification-action-method="<%= @data.dig(:action, :method) %>"
data-notification-timeout="<%= @data[:timeout] %>"
data-controller="notification">
<div class="rounded-lg shadow-xs overflow-hidden">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<span class="inline-block h-6 w-6 <%= @icon_color_class %>">
<%= render "icons/#{@icon_name}" %>
</span>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900">
<%= @data[:title] %>
</p>
<% if @data[:body].present? %>
<p class="mt-1 text-sm leading-5 text-gray-500">
<%= @data[:body] %>
</p>
<% end %>
<% if @data[:action].present? %>
<div class="mt-2" data-notification-target="buttons">
<a data-turbo-frame="_top" <% if @data.dig(:action, :method) == 'get' %> href="<%= @data.dig(:action, :url) %>" <% else %> href="#" data-action="notification#run" <% end %> class="text-sm leading-5 font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:underline transition ease-in-out duration-150">
<%= @data.dig(:action, :name) %>
</a>
<button data-action="notification#close" class="ml-6 text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:underline transition ease-in-out duration-150">
<%= t('.dismiss') %>
</button>
</div>
<% end %>
</div>
<div class="ml-4 flex-shrink-0 flex">
<button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150" data-action="notification#close">
<!-- Heroicon name: solid/x -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
</div>
<% if @data[:countdown] %>
<div class="bg-indigo-600 rounded-lg h-1 w-0" data-notification-target="countdown"></div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,54 @@
# frozen_string_literal: true
# @param type [String] Classic notification type `error`, `alert` and `info` + custom `success`
# @param data [String, Hash] `String` for backward compatibility,
# `Hash` for the new functionality `{title: '', body: '', timeout: 5, countdown: false, action: { url: '', method: '', name: ''}}`.
# The `title` attribute for `Hash` is mandatory.
class NotificationComponent < ViewComponent::Base
def initialize(type:, data:)
@type = type
@data = prepare_data(data)
@icon_name = icon_name
@icon_color_class = icon_color_class
@data[:timeout] ||= 5
@data[:action][:method] ||= "get" if @data[:action]
end
private
def prepare_data(data)
case data
when Hash
data
else
{ title: data }
end
end
def icon_name
case @type
when 'success'
'check-circle'
when 'error'
'alert-octagon'
when 'alert'
'alert-octagon'
else
'info'
end
end
def icon_color_class
case @type
when 'success'
'text-emerald-500'
when 'error'
'text-rose-600'
when 'alert'
'text-rose-600'
else
'text-gray-400'
end
end
end

View File

@@ -0,0 +1,4 @@
<%= link_to @path, class: @link_class do %>
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
<span class="truncate"><%= @name %></span>
<% end %>

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
class SidenavLinkComponent < ViewComponent::Base
def initialize(name:, path:, icon:, active: false, disabled: false)
@name = name
@path = path
@icon = icon
@active = active
@disabled = disabled
@link_class = class_names_link(path)
@icon_class = class_names_icon(path)
end
def class_names_link(path)
if @active
"bg-teal-50 border-teal-500 text-teal-700 hover:bg-teal-50 hover:text-teal-700 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
elsif @disabled
"border-transparent text-gray-400 hover:bg-gray-50 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
else
"border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
end
end
def class_names_icon(path)
if @active
"text-teal-500 group-hover:text-teal-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
elsif @disabled
"text-gray-300 group-hover:text-gray-300 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
else
"text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
end
end
end

View File

@@ -2,5 +2,10 @@ class Admin::BaseController < ApplicationController
before_action :authenticate_user!
before_action :authorize_admin
before_action :set_context
def set_context
@context = :admin
end
end

View File

@@ -1,4 +1,5 @@
class Admin::DashboardController < Admin::BaseController
def index
@current_section = :dashboard
end
end

View File

@@ -0,0 +1,79 @@
class Admin::DonationsController < Admin::BaseController
before_action :set_donation, only: [:show, :edit, :update, :destroy]
before_action :set_current_section, only: [:index, :show, :new, :edit]
# GET /donations
# GET /donations.json
def index
@donations = Donation.all
end
# GET /donations/1
# GET /donations/1.json
def show
end
# GET /donations/new
def new
@donation = Donation.new
end
# GET /donations/1/edit
def edit
end
# POST /donations
# POST /donations.json
def create
@donation = Donation.new(donation_params)
respond_to do |format|
if @donation.save
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
format.json { render :show, status: :created, location: @donation }
else
format.html { render :new }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /donations/1
# PATCH/PUT /donations/1.json
def update
respond_to do |format|
if @donation.update(donation_params)
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
format.json { render :show, status: :ok, location: @donation }
else
format.html { render :edit }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /donations/1
# DELETE /donations/1.json
def destroy
@donation.destroy
respond_to do |format|
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_donation
@donation = Donation.find(params[:id])
end
# Only allow a list of trusted parameters through.
def donation_params
params.require(:donation).permit(:user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :paid_at)
end
def set_current_section
@current_section = :donations
end
end

View File

@@ -0,0 +1,8 @@
class Admin::InvitationsController < Admin::BaseController
def index
@current_section = :invitations
@invitations_unused_count = Invitation.unused.count
@users_with_referrals_count = Invitation.used.distinct.count(:user_id)
@invitations_used = Invitation.used.order('used_at desc')
end
end

View File

@@ -1,12 +1,12 @@
class Admin::LdapUsersController < Admin::BaseController
before_action :set_current_section
def index
attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*")
if params[:ou]
treebase = "ou=#{params[:ou]},cn=users,dc=kosmos,dc=org"
else
treebase = "ou=kosmos.org,cn=users,dc=kosmos,dc=org"
end
@ou = params[:ou] || "kosmos.org"
treebase = "ou=#{@ou},cn=users,dc=kosmos,dc=org"
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.cn[0] }
@@ -25,7 +25,7 @@ class Admin::LdapUsersController < Admin::BaseController
private
def ldap_client
ldap_client ||= Net::LDAP.new host: ENV['LDAP_HOST'],
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
auth: {
@@ -38,4 +38,8 @@ class Admin::LdapUsersController < Admin::BaseController
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
def set_current_section
@current_section = :ldap_users
end
end

View File

@@ -9,6 +9,12 @@ class ApplicationController < ActionController::Base
end
end
def require_user_signed_out
if user_signed_in?
redirect_to root_path and return
end
end
def authorize_admin
http_status :forbidden unless current_user.is_admin?
end

View File

@@ -2,5 +2,6 @@ class DashboardController < ApplicationController
before_action :require_user_signed_in
def index
@current_section = :dashboard
end
end

View File

@@ -0,0 +1,10 @@
class DonationsController < ApplicationController
before_action :require_user_signed_in
# GET /donations
# GET /donations.json
def index
@donations = current_user.donations.completed
@current_section = :contributions
end
end

View File

@@ -0,0 +1,48 @@
class InvitationsController < ApplicationController
before_action :require_user_signed_in, except: ["show"]
before_action :require_user_signed_out, only: ["show"]
# GET /invitations
def index
@invitations_unused = current_user.invitations.unused
@invitations_used = current_user.invitations.used
@current_section = :invitations
end
# GET /invitations/a-random-invitation-token
def show
token = session[:invitation_token] = params[:id]
if Invitation.where(token: token, used_at: nil).exists?
redirect_to signup_path and return
else
flash.now[:alert] = "This invitation either doesn't exist or has already been used."
http_status :unauthorized
end
end
# POST /invitations
def create
@invitation = Invitation.new(user: current_user)
respond_to do |format|
if @invitation.save
format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' }
format.json { render :show, status: :created, location: @invitation }
else
format.html { render :new }
format.json { render json: @invitation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /invitations/1
def destroy
@invitation = current_user.invitations.find(params[:id])
@invitation.destroy
respond_to do |format|
format.html { redirect_to invitations_url }
format.json { head :no_content }
end
end
end

View File

@@ -0,0 +1,75 @@
class LnurlpayController < ApplicationController
before_action :find_user_by_address
MIN_SATS = 100
MAX_SATS = 1_000_000
MAX_COMMENT_CHARS = 100
def index
render json: {
status: "OK",
callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
tag: "payRequest",
maxSendable: MAX_SATS * 1000, # msat
minSendable: MIN_SATS * 1000, # msat
metadata: metadata(@user.address),
commentAllowed: MAX_COMMENT_CHARS
}
end
def invoice
amount = params[:amount].to_i / 1000 # msats
address = params[:address]
comment = params[:comment] || ""
if !valid_amount?(amount)
render json: { status: "ERROR", reason: "Invalid amount" }
return
end
if !valid_comment?(comment)
render json: { status: "ERROR", reason: "Comment too long" }
return
end
memo = "Sats for #{address}"
memo = "#{memo}: \"#{comment}\"" if comment.present?
payment_request = @user.ln_create_invoice({
amount: amount, # we create invoices in sats
memo: memo,
description_hash: Digest::SHA2.hexdigest(metadata(address)),
})
render json: {
status: "OK",
successAction: {
tag: "message",
message: "Sats received. Thank you!"
},
routes: [],
pr: payment_request
}
end
private
def find_user_by_address
address = params[:address].split("@")
@user = User.where(cn: address.first, ou: address.last).first
http_status :not_found if @user.nil?
end
def metadata(address)
"[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Send sats, receive thanks.\"]]"
end
def valid_amount?(amount_in_sats)
amount_in_sats <= MAX_SATS && amount_in_sats >= MIN_SATS
end
def valid_comment?(comment)
comment.length <= MAX_COMMENT_CHARS
end
end

View File

@@ -0,0 +1,7 @@
class SecurityController < ApplicationController
before_action :require_user_signed_in
def index
@current_section = :security
end
end

View File

@@ -0,0 +1,111 @@
class SignupController < ApplicationController
before_action :require_user_signed_out
before_action :require_invitation
before_action :set_invitation
before_action :set_new_user, only: ["steps", "validate"]
before_action :set_context
def index
@invited_by_name = @invitation.user.address
end
def steps
@step = params[:step].to_i
http_status :not_found unless [1,2,3].include?(@step)
@validation_error = session[:validation_error]
end
def validate
session[:validation_error] = nil
case user_params.keys.first
when "cn"
@user.cn = user_params[:cn]
@user.valid?
session[:new_user] = @user
if @user.errors[:cn].present?
session[:validation_error] = @user.errors[:cn].first # Store user including validation errors
redirect_to signup_steps_path(1) and return
else
redirect_to signup_steps_path(2) and return
end
when "email"
@user.email = user_params[:email]
@user.valid?
session[:new_user] = @user
if @user.errors[:email].present?
session[:validation_error] = @user.errors[:email].first # Store user including validation errors
redirect_to signup_steps_path(2) and return
else
redirect_to signup_steps_path(3) and return
end
when "password"
@user.password = user_params[:password]
@user.password_confirmation = user_params[:password]
@user.valid?
session[:new_user] = @user
if @user.errors[:password].present?
session[:validation_error] = @user.errors[:password].first # Store user including validation errors
redirect_to signup_steps_path(3) and return
else
complete_signup
msg = "Almost done! We have sent you an email to confirm your address."
redirect_to(check_your_email_path, notice: msg) and return
end
end
end
private
def user_params
params.require(:user).permit(:cn, :email, :password)
end
def require_invitation
if session[:invitation_token].blank?
flash.now[:alert] = "You need an invitation to sign up for an account."
http_status :unauthorized
elsif !valid_invitation?(session[:invitation_token])
flash.now[:alert] = "This invitation either doesn't exist or has already been used."
http_status :unauthorized
end
@invitation = Invitation.find_by(token: session[:invitation_token])
end
def valid_invitation?(token)
Invitation.where(token: session[:invitation_token], used_at: nil).exists?
end
def set_invitation
@invitation = Invitation.find_by(token: session[:invitation_token])
end
def set_new_user
if session[:new_user].present?
@user = User.new(session[:new_user])
else
@user = User.new(ou: "kosmos.org")
end
end
def complete_signup
session[:new_user] = nil
session[:validation_error] = nil
CreateAccount.call(
username: @user.cn,
domain: "kosmos.org",
email: @user.email,
password: @user.password,
invitation: @invitation
)
end
def set_context
@context = :signup
end
end

View File

@@ -0,0 +1,47 @@
require "rqrcode"
class WalletController < ApplicationController
before_action :require_user_signed_in
before_action :authenticate_with_lndhub
def index
@current_section = :wallet
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
qrcode = RQRCode::QRCode.new(@wallet_url)
@svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
@balance = fetch_balance rescue nil
end
private
def authenticate_with_lndhub
if session["ln_auth_token"].present?
@ln_auth_token = session["ln_auth_token"]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session["ln_auth_token"] = auth_token
@ln_auth_token = auth_token
end
rescue
# TODO add exception tracking
end
def fetch_balance
lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token
data["BTC"]["AvailableBalance"]
end
end

View File

@@ -1,2 +1,14 @@
module ApplicationHelper
def sats_to_btc(sats)
sats.to_f / 100000000
end
def main_nav_class(current_section, link_to_section)
if current_section == link_to_section
"bg-gray-900/50 text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block"
else
"text-gray-300 hover:bg-gray-900/30 hover:text-white active:bg-gray-900/30 active:text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block"
end
end
end

View File

@@ -0,0 +1,2 @@
module DonationsHelper
end

View File

@@ -0,0 +1,2 @@
module InvitationsHelper
end

View File

@@ -0,0 +1,2 @@
module LnurlpayHelper
end

View File

@@ -0,0 +1,2 @@
module SignupHelper
end

View File

@@ -0,0 +1,2 @@
module WalletHelper
end

View File

@@ -0,0 +1,3 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"

View File

@@ -1,6 +0,0 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()

View File

@@ -1,5 +0,0 @@
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)

View File

@@ -0,0 +1,9 @@
import { Application } from "@hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }

View File

@@ -0,0 +1,11 @@
// Import and register all your controllers from the importmap under controllers/*
import { application } from "controllers/application"
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

View File

@@ -0,0 +1,110 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["buttons", "countdown"]
connect() {
const timeoutSeconds = parseInt(this.data.get("timeout"));
setTimeout(() => {
this.element.classList.remove('hidden');
this.element.classList.add('notification-enter', 'notification-enter-from');
// Trigger transition
setTimeout(() => {
this.element.classList.add('notification-enter-to');
}, 100);
// Trigger countdown
if (this.hasCountdownTarget) {
this.countdownTarget.style.animation = 'notification-countdown linear ' + timeoutSeconds + 's';
}
}, 500);
this.timeoutId = setTimeout(() => {
this.close();
}, timeoutSeconds * 1000 + 500);
}
run(e) {
e.preventDefault();
this.stop();
let _this = this;
this.buttonsTarget.innerHTML = '<span class="text-sm leading-5 font-medium text-grey-700">Processing...</span>';
// Call the action
fetch(this.data.get("action-url"), {
method: this.data.get("action-method").toUpperCase(),
dataType: 'script',
credentials: "include",
headers: {
"X-CSRF-Token": this.csrfToken
},
})
.then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then(response => response.json())
.then(data => {
// Set new content
_this.buttonsTarget.innerHTML = '<span class="text-sm leading-5 font-medium text-green-700">' + data.message + '</span>';
// Remove hidden class and display the record
if (data.inline) {
document.getElementById(data.dom_id).classList.toggle('hidden');
}
// Close
setTimeout(() => {
if (data.inline) {
// Just close the notification
_this.close();
} else {
// Reload the page using Turbo
window.Turbo.visit(window.location.toString(), {action: 'replace'})
}
}, 1000);
})
.catch(error => {
console.log(error);
_this.buttonsTarget.innerHTML = '<span class="text-sm leading-5 font-medium text-red-700">Error!</span>';
setTimeout(() => {
_this.close();
}, 1000);
});
}
stop() {
clearTimeout(this.timeoutId)
this.timeoutId = null
}
continue() {
this.timeoutId = setTimeout(() => {
this.close();
}, parseInt(this.data.get("timeout")));
}
close() {
this.element.classList.remove('notification-enter', 'notification-enter-from', 'notification-enter-to');
this.element.classList.add('notification-leave', 'notification-leave-from')
// Trigger transition
setTimeout(() => {
this.element.classList.add('notification-leave-to');
}, 100);
// Remove element after transition
setTimeout(() => {
this.element.remove();
}, 300);
}
get csrfToken() {
const element = document.head.querySelector('meta[name="csrf-token"]')
return element.getAttribute("content")
}
}

View File

@@ -0,0 +1,31 @@
import { Controller } from "@hotwired/stimulus"
function show (element) {
element.classList.add('block');
element.classList.remove('hidden');
}
function hide (element) {
element.classList.remove('block');
element.classList.add('hidden');
}
export default class extends Controller {
static targets = [ 'mobileMenu', 'iconMobileMenuOpen', 'iconMobileMenuClose' ];
connect() {
this.mobileMenuTarget.classList.add('hidden');
}
toggleMobileNav() {
if (this.mobileMenuTarget.classList.contains('hidden')) {
show(this.mobileMenuTarget);
show(this.iconMobileMenuCloseTarget);
hide(this.iconMobileMenuOpenTarget);
} else {
hide(this.mobileMenuTarget);
hide(this.iconMobileMenuCloseTarget);
show(this.iconMobileMenuOpenTarget);
}
}
}

View File

@@ -1,16 +0,0 @@
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("channels")
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

View File

@@ -0,0 +1,32 @@
class CreateLdapUserJob < ApplicationJob
queue_as :default
def perform(username, domain, email, hashed_pw)
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
attr = {
objectclass: ["top", "account", "person", "extensibleObject"],
cn: username,
sn: username,
uid: username,
mail: email,
userPassword: hashed_pw
}
ldap_client.add(dn: dn, attributes: attr)
end
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],
password: ldap_config['admin_password']
}
end
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
end

View File

@@ -0,0 +1,13 @@
class CreateLndhubWalletJob < ApplicationJob
queue_as :default
def perform(user)
return if user.ln_login.present? && user.ln_password.present?
lndhub = Lndhub.new
credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
user.update! ln_login: credentials["login"],
ln_password: credentials["password"]
end
end

View File

@@ -0,0 +1,18 @@
class ExchangeXmppContactsJob < ApplicationJob
queue_as :default
def perform(inviter, username, domain)
ejabberd = EjabberdApiClient.new
ejabberd.add_rosteritem({
"localuser": username, "localhost": domain,
"user": inviter.cn, "host": inviter.ou,
"nick": inviter.cn, "group": "Friends", "subs": "both"
})
ejabberd.add_rosteritem({
"localuser": inviter.cn, "localhost": inviter.ou,
"user": username, "host": domain,
"nick": username, "group": "Friends", "subs": "both"
})
end
end

View File

@@ -0,0 +1,15 @@
require 'mail'
module EmailValidatable
extend ActiveSupport::Concern
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
begin
a = Mail::Address.new(value)
rescue Mail::Field::ParseError
record.errors[attribute] << (options[:message] || "is not a valid address")
end
end
end
end

13
app/models/donation.rb Normal file
View File

@@ -0,0 +1,13 @@
class Donation < ApplicationRecord
# Relations
belongs_to :user
# Validations
validates_presence_of :amount_sats
# Hooks
# TODO before_create :store_fiat_value
#Scopes
scope :completed, -> { where.not(paid_at: nil) }
end

20
app/models/invitation.rb Normal file
View File

@@ -0,0 +1,20 @@
class Invitation < ApplicationRecord
# Relations
belongs_to :user
# Validations
validates_presence_of :user
# Hooks
before_create :generate_token
# Scopes
scope :unused, -> { where(used_at: nil) }
scope :used, -> { where.not(used_at: nil) }
private
def generate_token
self.token = SecureRandom.hex(8)
end
end

View File

@@ -1,4 +1,18 @@
class User < ApplicationRecord
include EmailValidatable
# Relations
has_many :invitations, dependent: :destroy
has_many :donations, dependent: :nullify
validates_uniqueness_of :cn
validates_length_of :cn, :minimum => 3
validates_uniqueness_of :email
validates :email, email: true
lockbox_encrypts :ln_login
lockbox_encrypts :ln_password
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :ldap_authenticatable,
@@ -33,4 +47,19 @@ class User < ApplicationRecord
false
end
end
def address
"#{self.cn}@#{self.ou}"
end
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
def ln_create_invoice(payload)
lndhub = Lndhub.new
lndhub.authenticate self
lndhub.addinvoice payload
end
end

View File

@@ -0,0 +1,7 @@
class ApplicationService
# This enables executing a service's `#call` method directly via
# `MyService.call(args)`, without creating a class instance it first.
def self.call(*args, &block)
new(*args, &block).call
end
end

View File

@@ -0,0 +1,66 @@
class CreateAccount < ApplicationService
def initialize(args)
@username = args[:username]
@domain = args[:ou] || "kosmos.org"
@email = args[:email]
@password = args[:password]
@invitation = args[:invitation]
end
def call
user = create_user_in_database
add_ldap_document
create_lndhub_wallet(user)
if @invitation.present?
update_invitation(user.id)
exchange_xmpp_contacts
end
end
private
def create_user_in_database
User.create!(
cn: @username,
ou: @domain,
email: @email,
password: @password,
password_confirmation: @password
)
end
def update_invitation(user_id)
@invitation.update! invited_user_id: user_id, used_at: DateTime.now
end
# TODO move to confirmation
def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
end
def exchange_xmpp_contacts
#TODO enable in development when we have easy setup of ejabberd etc.
return if Rails.env.development?
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
end
def create_lndhub_wallet(user)
CreateLndhubWalletJob.perform_later(user)
end
def exchange_xmpp_contacts_between_inviter_and_invitee
ejabberd = EjabberdApiClient.new
EjabberdApiClient.add_roster_item({
"localuser": @username,
"localhost": @domain,
"user": @inviter.cn,
"host": @inviter.ou,
"nick": @username,
"group": "Friends",
"subs": "both"
})
end
end

View File

@@ -0,0 +1,20 @@
class EjabberdApiClient
def initialize
@base_url = ENV["EJABBERD_API_URL"]
end
def post(endpoint, payload)
res = Faraday.post("#{@base_url}/#{endpoint}", payload.to_json,
"Content-Type" => "application/json")
if res.status != 200
Rails.logger.error "[ejabberd] API request failed:"
Rails.logger.error res.body
#TODO add some kind of exception tracking/notifications
end
end
def add_rosteritem(payload)
post "add_rosteritem", payload
end
end

58
app/services/lndhub.rb Normal file
View File

@@ -0,0 +1,58 @@
class Lndhub
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_API_URL"]
end
def post(endpoint, payload)
headers = { "Content-Type" => "application/json" }
if auth_token
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
end
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
if res.status != 200
Rails.logger.error "[lndhub] API request failed:"
Rails.logger.error res.body
#TODO add some kind of exception tracking/notifications
end
JSON.parse(res.body)
end
def get(endpoint, auth_token)
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "Bearer #{auth_token}"
})
JSON.parse(res.body)
end
def create(payload)
post "create", payload
end
def authenticate(user)
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end
def balance(user_token)
get "balance", user_token || auth_token
end
def addinvoice(payload)
invoice = post "addinvoice", {
amt: payload[:amount],
memo: payload[:memo],
description_hash: payload[:description_hash]
}
invoice["payment_request"]
end
end

View File

@@ -1,7 +1,7 @@
<h2>Admin Panel</h2>
<p>
Ohai there, admin human.
</p>
<p>
<%= link_to 'LDAP users', admin_ldap_users_path %>
</p>
<%= render HeaderComponent.new(title: "Admin Panel") %>
<%= render MainSimpleComponent.new do %>
<p class="text-center">
With great power comes great responsibility.
</p>
<% end %>

View File

@@ -0,0 +1,2 @@
json.extract! donation, :id, :user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :created_at, :updated_at
json.url donation_url(donation, format: :json)

View File

@@ -0,0 +1,58 @@
<%= form_with(url: url, model: donation, local: true) do |form| %>
<% if donation.errors.any? %>
<div id="error_explanation">
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
<ul>
<% donation.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<p>
<%= form.label :user_id %>
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_sats, "Amount BTC (sats)" %>
<%= form.number_field :amount_sats %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_eur, "Amount EUR (cents)" %>
<%= form.number_field :amount_eur %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_usd, "Amount USD (cents)"%>
<%= form.number_field :amount_usd %>
</p>
</div>
<div class="field">
<p>
<%= form.label :public_name %>
<%= form.text_field :public_name %>
</p>
</div>
<div class="field">
<p>
<%= form.label :paid_at %>
<%= form.text_field :paid_at %>
</p>
</div>
<p class="mt-8">
<%= form.submit class: 'btn-md btn-blue' %>
</p>
<% end %>

View File

@@ -0,0 +1,12 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render MainSimpleComponent.new do %>
<h2>Editing Donation</h2>
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
<p class="mt-8">
<%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> |
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
<p>
<% end %>

View File

@@ -0,0 +1,44 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render MainSimpleComponent.new do %>
<% if @donations.any? %>
<table class="w-full">
<thead>
<tr class="text-left">
<th>User</th>
<th>Amount BTC</th>
<th>in EUR</th>
<th>in USD</th>
<th>Public name</th>
<th>Date</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @donations.each do |donation| %>
<tr>
<td><%= donation.user.address %></td>
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
<td><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
<td><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
<td><%= donation.public_name %></td>
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
<td><%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>
No donations yet.
</p>
<% end %>
<p class="mt-12">
<%= link_to 'Record an out-of-system donation', new_admin_donation_path, class: 'btn-md btn-gray' %>
</p>
<% end %>

View File

@@ -0,0 +1 @@
json.array! @donations, partial: "donations/donation", as: :donation

View File

@@ -0,0 +1,11 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render MainSimpleComponent.new do %>
<h2>New Donation</h2>
<%= render 'form', donation: @donation, url: admin_donations_path %>
<p class="mt-8">
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
</p>
<% end %>

View File

@@ -0,0 +1,38 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render MainSimpleComponent.new do %>
<p>
<strong>User:</strong>
<%= @donation.user.address %>
</p>
<p>
<strong>Amount sats:</strong>
<%= @donation.amount_sats %>
</p>
<p>
<strong>Amount eur:</strong>
<%= @donation.amount_eur %>
</p>
<p>
<strong>Amount usd:</strong>
<%= @donation.amount_usd %>
</p>
<p>
<strong>Public name:</strong>
<%= @donation.public_name %>
</p>
<p>
<strong>Date:</strong>
<%= @donation.paid_at %>
</p>
<p class="mt-8">
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> |
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
</p>
<% end %>

View File

@@ -0,0 +1 @@
json.partial! "donations/donation", donation: @donation

View File

@@ -0,0 +1,35 @@
<%= render HeaderComponent.new(title: "Invitations") %>
<%= render MainSimpleComponent.new do %>
<section>
<p>
There are currently <strong><%= @invitations_unused_count %>
unused invitations</strong> available to existing users.
<strong><%= @users_with_referrals_count %> users</strong> have successfully
invited new users.
</p>
</section>
<% if @invitations_used.any? %>
<section>
<h3>Accepted (<%= @invitations_used.length %>)</h3>
<table>
<thead>
<tr class="text-left">
<th>Token</th>
<th>Accepted</th>
<th>Invited user</th>
</tr>
</thead>
<tbody>
<% @invitations_used.each do |invitation| %>
<tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
<td><%= User.find(invitation.invited_user_id).address %></td>
</tr>
<% end %>
</tbody>
</table>
</section>
<% end %>
<% end %>

View File

@@ -1,27 +1,34 @@
<h2>LDAP users</h2>
<%= render HeaderComponent.new(title: "LDAP Users: #{@ou}") %>
<ul>
<li><%= link_to 'kosmos.org', admin_ldap_users_path %></li>
<li><%= link_to '5apps.com', admin_ldap_users_path(ou: '5apps.com') %></li>
</ul>
<%= render MainSimpleComponent.new do %>
<h3 class="hidden">Domains</h3>
<ul class="mb-10">
<li class="inline-block">
<%= link_to 'kosmos.org', admin_ldap_users_path, class: "ks-text-link" %>
</li>
<li class="inline-block ml-6">
<%= link_to '5apps.com', admin_ldap_users_path(ou: '5apps.com'), class: "ks-text-link" %>
</li>
</ul>
<table>
<thead>
<tr>
<th>UID</th>
<th>E-Mail</th>
<th>Admin</th>
<!-- <th>Password</th> -->
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><%= entry[:uid] %></td>
<td><%= entry[:mail] %></td>
<td><%= entry[:admin] %></td>
<!-- <td><%= entry[:password] %></td> -->
</tr>
<% end %>
</tbody>
</table>
<table>
<thead>
<tr class="text-left">
<th>UID</th>
<th>E-Mail</th>
<th>Admin</th>
<!-- <th>Password</th> -->
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><%= entry[:uid] %></td>
<td><%= entry[:mail] %></td>
<td><%= entry[:admin] %></td>
<!-- <td><%= entry[:password] %></td> -->
</tr>
<% end %>
</tbody>
</table>
<% end %>

View File

@@ -1,42 +1,61 @@
<section>
<h2>Services</h2>
<p>
Your Kosmos account and password currently give you access to these
services:
</p>
<div class="grid services">
<div class="grid-item chat">
<h3><%= link_to "Chat", "https://wiki.kosmos.org/Services:XMPP" %></h3>
<p>
Chat rooms and instant messaging (XMPP/Jabber)
</p>
</div>
<div class="grid-item gitea">
<h3><%= link_to "Gitea", "https://gitea.kosmos.org" %></h3>
<p>
Code hosting and collaboration for software projects
</p>
</div>
<div class="grid-item wiki">
<h3><%= link_to "Wiki", "https://wiki.kosmos.org" %></h3>
<p>
Kosmos documentation and knowledge base
</p>
</div>
<div class="grid-item discourse">
<h3><%= link_to "Discourse", "https://community.kosmos.org" %></h3>
<p>
Kosmos community forums and user support/help site
</p>
</div>
</div>
</section>
<%= render HeaderComponent.new(title: "Services") %>
<section>
<h3>Password change</h3>
<p>
<%= form_with(url: settings_reset_password_path, method: :post) do %>
<%= submit_tag("Send me a password reset link") %>
<% end %>
</p>
</section>
<%= render MainSimpleComponent.new do %>
<section>
<p>
Your Kosmos account and password currently give you access to these
services:
</p>
<div class="grid services mt-12">
<div>
<h3 class="mb-3.5">
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Chat rooms and instant messaging (XMPP/Jabber)
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Kosmos community forums and user support/help site
</p>
</div>
<div>
<h3 class="mb-3.5">
<span class="text-yellow-500">🗲</span>
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Send and receive sats over the Bitcoin Lightning Network
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Kosmos documentation and knowledge base
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Code hosting and collaboration for software projects
</p>
</div>
<div>
<h3 class="mb-3.5">
<%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %>
</h3>
<p class="text-gray-500">
Continuous integration for software projects on Gitea
</p>
</div>
</div>
</section>
<% end %>

View File

@@ -1,20 +1,22 @@
<h2>Resend confirmation instructions</h2>
<%= render HeaderCompactComponent.new(title: "Log in") %>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= render MainCompactComponent.new do %>
<h2>Resend confirmation instructions</h2>
<div class="field">
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<p>
<%= f.label :email, 'Email address' %><br />
<%= f.email_field :email, required: true, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
<%= f.label :email, 'Email address', class: 'block mb-1 w-full' %>
<%= f.email_field :email,
required: true, autofocus: true, autocomplete: "email",
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
class: "w-full" %>
</p>
</div>
<p class="mt-8">
<%= f.submit "Resend confirmation link",
class: 'btn-md btn-blue w-full sm:w-auto' %>
</p>
<% end %>
<div class="actions">
<p>
<%= f.submit "Resend confirmation instructions" %>
</p>
</div>
<%= render "devise/shared/links" %>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,25 +1,31 @@
<h2>Change your password</h2>
<%= render HeaderCompactComponent.new(title: "Log in") %>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<%= render MainCompactComponent.new do %>
<h2>Change your password</h2>
<div class="field">
<%= f.label :password, "New password" %><br />
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em><br />
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
</div>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<div class="field">
<%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<p class="mb-1">
<%= f.label :password, "New password" %>
</p>
<p>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
<% end %>
</p>
<p class="mb-1">
<%= f.label :password_confirmation, "Confirm new password" %>
</p>
<p>
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</p>
<p class="mt-8">
<%= f.submit "Change my password", class: 'btn-md btn-blue' %>
</p>
<% end %>
<div class="actions">
<%= f.submit "Change my password" %>
</div>
<%= render "devise/shared/links" %>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,27 +1,25 @@
<h2>Forgot your password?</h2>
<%= render HeaderCompactComponent.new(title: "Log in") %>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= render MainCompactComponent.new do %>
<h2>Forgot your password?</h2>
<div class="field">
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<p>
<%= f.label :cn, 'User' %><br />
<%= f.text_field :cn, autofocus: true, autocomplete: "username", required: true %> @ kosmos.org
<%= f.label :cn, 'User', class: 'block' %>
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
required: true, class: "w-full md:w-3/5"%>
<span class="ml-1 text-gray-500">@ kosmos.org</span>
</p>
</div>
<div class="field">
<p>
<%= f.label :email, 'Email address' %><br />
<%= f.email_field :email, autocomplete: "email", required: true %>
<%= f.label :email, 'Email address', class: 'block' %>
<%= f.email_field :email, autocomplete: "email", required: true,
class: "w-full md:w-3/5"%>
</p>
</div>
<p class="mt-8">
<%= f.submit "Send me a reset link", class: 'btn-md btn-blue w-full sm:w-auto' %>
</p>
<% end %>
<div class="actions">
<p>
<%= f.submit "Send me reset password instructions" %>
</p>
</div>
<%= render "devise/shared/links" %>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -31,9 +31,9 @@
<%= f.password_field :current_password, autocomplete: "current-password" %>
</div>
<div class="actions">
<%= f.submit "Update" %>
</div>
<p class="mt-8">
<%= f.submit "Update", class: 'btn-md btn-blue' %>
</p>
<% end %>
<h3>Cancel my account</h3>

View File

@@ -1,25 +1,23 @@
<h2>Log in</h2>
<%= render HeaderCompactComponent.new(title: "Log in") %>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= render MainCompactComponent.new do %>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<p>
<%= f.label :cn, 'User', class: 'block' %>
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
class: "w-full md:w-3/5"%>
<span class="ml-1 text-gray-500">@ kosmos.org</span>
</p>
<p>
<%= f.label :password, class: 'block' %>
<%= f.password_field :password, autocomplete: "current-password",
class: "w-full md:w-3/5"%>
</p>
<p class="mt-8">
<%= f.submit "Log in", class: 'btn-md btn-blue w-full sm:w-auto' %>
</p>
<% end %>
<div class="field">
<p>
<%= f.label :cn, 'User' %><br />
<%= f.text_field :cn, autofocus: true, autocomplete: "username" %> @ kosmos.org
</p>
</div>
<div class="field">
<p>
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</p>
</div>
<div class="actions">
<p>
<%= f.submit "Log in" %>
</p>
</div>
<%= render "devise/shared/links" %>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -1,24 +1,24 @@
<div class="devise-links">
<div class="devise-links mt-8 text-sm">
<%- if controller_name != 'sessions' %>
<p>
<p class="mb-1.5">
<%= link_to "Log in", new_session_path(resource_name) %><br />
</p>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p>
<p class="mb-1.5">
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
</p>
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<p>
<p class="mb-1.5">
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
</p>
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<p>
<p class="mb-1.5">
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
</p>
<% end %>

View File

@@ -2,15 +2,13 @@
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<p>
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="actions">
<%= f.submit "Resend unlock instructions" %>
</div>
</p>
<p class="mt-8">
<%= f.submit "Resend unlock instructions", class: 'btn-md btn-blue' %>
</p>
<% end %>
<%= render "devise/shared/links" %>

View File

@@ -0,0 +1,41 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render MainSimpleComponent.new do %>
<section>
<p class="mb-12">
Your financial contributions to the development and upkeep of Kosmos
software and services.
</p>
<% if @donations.any? %>
<ul class="list-none">
<% @donations.each do |donation| %>
<li class="mb-8 grid gap-y-2 gap-x-8 grid-cols-2 items-center">
<h3 class="mb-0">
<%= donation.paid_at.strftime("%B %d, %Y") %>
</h3>
<p class="row-span-2 font-mono text-right mb-0">
<span class="text-xl">
<%= number_with_delimiter donation.amount_sats %> sats
</span>
<br>
<span class="text-sm text-gray-500">
(~ <%= number_to_currency donation.amount_eur / 100, unit: "" %> EUR)
</span>
</p>
<p class="mb-0">
<% if donation.public_name.present? %>
Public name: <%= donation.public_name %>
<% else %>
Anonymous
<% end %>
</p>
</li>
<% end %>
</ul>
<% else %>
<p class="text-gray-500">
No donations to show.
</p>
<% end %>
</section>
<% end %>

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