107 Commits

Author SHA1 Message Date
Râu Cao
5075fef616 Only show avatar when available on admin user page
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-25 22:16:16 +02:00
Râu Cao
8e090daa9c Fetch web app metadata when creating RS auth 2023-10-25 22:16:16 +02:00
Râu Cao
def87a1621 Remove variants from attachment 2023-10-25 22:16:16 +02:00
Râu Cao
00ec7fa21c WIP Add RS auths/apps to Storage dashboard 2023-10-25 22:16:13 +02:00
Râu Cao
2b8bfaaca8 Add admin page for web apps
All checks were successful
continuous-integration/drone/push Build is passing
2023-10-24 22:42:16 +02:00
Râu Cao
3e9a08a266 Remove (long) obsolete edge case 2023-10-24 17:29:24 +02:00
Râu Cao
fcea11f0e5 Associate RS authorizations with web apps 2023-10-24 17:29:24 +02:00
Râu Cao
261a782963 Only complete icon URLs when given relative or absolute paths 2023-10-24 17:29:24 +02:00
Râu Cao
e964e7e52c Save web app metadata explicitly 2023-10-24 17:29:24 +02:00
Râu Cao
e508407df4 Remove debug statement 2023-10-24 17:29:23 +02:00
Râu Cao
bec827acb1 Store web app icons with proper folder paths 2023-10-24 17:29:23 +02:00
Râu Cao
0a69603643 Update web app metadata when first creating a record 2023-10-24 17:29:23 +02:00
Râu Cao
d4f71e98ed Download and attach icons for web apps 2023-10-24 17:29:23 +02:00
Râu Cao
e56c9bd0d5 Add web app model, service to fetch metadata 2023-10-24 17:29:23 +02:00
Râu Cao
e1b7e1b2ef Update dependencies, add manifique 2023-10-24 17:29:23 +02:00
Râu Cao
1056ffd08e Add optional S3 config/backend for ActiveStorage 2023-10-24 17:29:23 +02:00
be5fe00f20 Merge pull request 'Fix XMPP from-address config not being used' (#150) from bugfix/xmpp_from_address into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #150
2023-10-19 10:47:45 +00:00
Râu Cao
e9c4929726 Fix XMPP from-address config not being used
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-10-17 15:21:57 +02:00
14ff0c0e16 Merge pull request 'BTCPay settings, admin page, and new Lightning balance API' (#147) from feature/btcpay_configs into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #147
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-26 10:13:09 +00:00
Râu Cao
d939f5d649 Merge branch 'master' into feature/btcpay_configs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-09-20 19:12:24 +02:00
Râu Cao
69fffb29d8 Make publishing of BTCPay wallet balances optional
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2023-09-20 18:36:53 +02:00
Râu Cao
91d3b977e9 Fix spec 2023-09-20 18:26:50 +02:00
7a5fd46835 Merge pull request 'Add user avatars to LDAP, upload on profile settings page' (#148) from feature/123-user_avatars into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #148
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-13 13:01:25 +00:00
Râu Cao
9c4c5c2553 Use correct content type for image
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-09-13 14:49:16 +02:00
Râu Cao
8f819d12c0 Remove debug output 2023-09-13 14:48:51 +02:00
Râu Cao
b810e27480 Use custom docker image with libvips installed in CI
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:40:43 +02:00
Râu Cao
1949f1876f Use attr_reader instead of shared instance variables
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:22:15 +02:00
Râu Cao
2ba0116ca6 Fix wrong inheritance
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:17:46 +02:00
Râu Cao
2c2ddabdff Fix code being silly
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:15:14 +02:00
Râu Cao
dfcdbec0dd Add specs for avatar upload
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-09-07 11:42:42 +02:00
Râu Cao
3b67a8791c Add libvips package to Docker container 2023-09-07 11:42:24 +02:00
Râu Cao
d5ab532947 Store and retrieve avatars in/from LDAP exclusively
Some checks failed
continuous-integration/drone/push Build is failing
No need to keep them in two places at the same time. We can fetch them
from LDAP whenever we want to do something with them.
2023-09-06 20:42:26 +02:00
Râu Cao
50c63d5c38 Update user avatar in LDAP 2023-09-06 19:02:07 +02:00
Râu Cao
64d09cfb7f Use variant declarations instead of custom methods 2023-09-06 12:38:47 +02:00
Râu Cao
def44618ef Comments
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-06 12:16:00 +02:00
Râu Cao
9e5aeaf572 Add user avatars 2023-09-06 12:15:53 +02:00
Râu Cao
86f85a90f4 Add/configure ActiveStorage 2023-09-06 12:14:28 +02:00
d8a35ac3fd Merge pull request 'Fix wrong redirect after sign-in for RS OAuth' (#146) from bugfix/rs_oauth_login into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #146
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-05 11:03:02 +00:00
Râu Cao
5a5f62e98a Refactor BTCPay service and API, add lightning balance
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-04 16:02:54 +02:00
Râu Cao
074f9afcbb Fix descriptions not being shown for resettable form fields 2023-09-04 15:37:02 +02:00
Râu Cao
725fd2e5ea Move lndhub admin token to env var/setting 2023-09-04 15:36:22 +02:00
Râu Cao
8349ca5e12 Add admin settings page for BTCPay 2023-09-04 15:25:20 +02:00
Râu Cao
46d59e3371 Improve icons in admin service settings sidenav 2023-09-04 15:24:35 +02:00
Râu Cao
e8e6ee0bc4 Add configurable settings for BTCPay 2023-09-04 15:23:27 +02:00
Râu Cao
a91ee2bd0a Fix generated usernames in seeds potentially being too short
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-04 11:35:51 +02:00
Râu Cao
fcb6923c92 Fix wrong redirect after sign-in for RS OAuth
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
We use a custom auth method to pre-fill the username when reaching the
RS OAuth while signed out. However, it needs to redirect back to the RS
OAuth page after sign-in, and not to the root path.
2023-09-04 11:33:16 +02:00
Râu Cao
0f3b9f176e 0.8.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-03 15:35:46 +02:00
822ae2f945 Merge pull request 'Fix migration failing with PostgreSQL' (#145) from bugfix/144-postgres_migration into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #145
2023-09-03 13:32:36 +00:00
Râu Cao
96c669ab4e Update database schema, fix spec
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2023-09-03 15:29:09 +02:00
Râu Cao
558100c35e Fix migration failing with PostgreSQL 2023-09-03 15:28:32 +02:00
Râu Cao
6739b38f4c 0.8.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-01 12:18:26 +02:00
7e1272c936 Merge pull request 'Service pages for Chat and Social' (#143) from feature/service_pages into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #143
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-01 08:36:09 +00:00
Râu Cao
ecdeb4c122 Fix copypasta
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-09-01 10:32:11 +02:00
Râu Cao
8614e2f12b Use service configs on dashboard
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Only show enabled services, and use the URLs from the various configs.
2023-08-13 17:24:10 +02:00
Râu Cao
a038a857d9 Make Drone CI configurable 2023-08-13 17:23:57 +02:00
Râu Cao
eee81d0cf1 Small link improvement
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 15:41:57 +02:00
Râu Cao
b7fa4b012a Allow Mastodon address domain to be different from primary domain
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 12:27:05 +02:00
Râu Cao
10bcd5c32b Ignore .env.development 2023-08-13 12:26:56 +02:00
Râu Cao
f79d5d4724 Use select element instead of tabs on mobile
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-11 14:17:12 +02:00
Râu Cao
866ffbe615 Upgrade tailwindcss-stimulus-components to latest version
All checks were successful
continuous-integration/drone/push Build is passing
The latest one offers more tabs features. Required some changes to the
modals and tabs code.
2023-08-11 13:58:57 +02:00
Râu Cao
3c1fe3396d Add Mastodon service page 2023-08-11 13:58:53 +02:00
Râu Cao
e4242333d9 Add recommended apps for Chat/XMPP
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-08 19:59:29 +02:00
Râu Cao
138f13c1a0 Add note
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-07 18:16:40 +02:00
Râu Cao
ad5e515200 Update README 2023-08-07 18:16:34 +02:00
Râu Cao
1ea8b22a59 WIP Add service page for Chat
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-07 18:16:14 +02:00
Râu Cao
f49aff262c Add base controller for service controllers
Some checks are pending
continuous-integration/drone/push Build is running
2023-08-07 18:15:17 +02:00
852e2fea1e Merge pull request 'remoteStorage OAuth' (#109) from feature/rs-oauth into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #109
2023-08-04 08:55:28 +00:00
Râu Cao
353b55fe1a Add RS OAuth controller specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-08-01 14:29:24 +02:00
Râu Cao
ba0cbba96b Add feature spec for RS OAuth dialog
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-01 13:01:41 +02:00
Râu Cao
5f921f1b53 RS OAuth pre-fills username for login 2023-08-01 13:01:03 +02:00
Râu Cao
a2d27bf575 Support pre-filling of username in login form 2023-08-01 13:00:22 +02:00
Râu Cao
fcf9a065e1 Fix specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-14 15:56:28 +02:00
Râu Cao
ec9bcacd46 Add specs for RemoteStorageAuthorization model
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-07-14 15:31:20 +02:00
Râu Cao
645abac810 Rename RS token expiry job 2023-07-14 15:29:29 +02:00
Râu Cao
e11be727a1 Indentation 2023-07-14 15:29:04 +02:00
Râu Cao
12b24337e7 Fix typo 2023-07-14 15:28:45 +02:00
Râu Cao
b0bfc290c4 Refactor code for newer Redis 2023-07-14 15:28:09 +02:00
Râu Cao
4c6c81171b Fix typo 2023-07-14 15:27:57 +02:00
Râu Cao
4d88a40109 Add separate config for RS Redis 2023-07-14 15:27:30 +02:00
Râu Cao
d9b39b36fb Merge branch 'master' into feature/rs-oauth
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-13 15:31:28 +02:00
Râu Cao
06aed8c33d Count up minor version on feature merge
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-13 15:26:35 +02:00
0a778e92d8 Merge pull request 'Add modal component, QR codes for invite links' (#140) from feature/139-qr_codes into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #140
2023-07-13 13:24:12 +00:00
Râu Cao
e5a5633e44 Add Redis config for dev with Redis on localhost
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-04 17:00:04 +02:00
Râu Cao
a68825493f Add Redis config in CI
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-04 16:44:11 +02:00
Râu Cao
e1e83386a8 Merge branch 'master' into feature/rs-oauth 2023-07-04 16:43:32 +02:00
Râu Cao
3adc1917f6 Improve outline button style, use everywhere
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-06-27 19:23:20 +02:00
Râu Cao
8a570ce724 Use modal component for LndHub setup code 2023-06-27 19:23:20 +02:00
Râu Cao
c78df9e5f1 Add QR code icon, button, modal for invites
Using https://excid3.github.io/tailwindcss-stimulus-components/
2023-06-27 19:23:20 +02:00
5c2df3df07 Add Redis service to Drone config
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-06-27 15:07:28 +02:00
83e3e2ecd8 Merge pull request 'Allow editing and resetting of all admin setting strings' (#137) from feature/admin_settings into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #137
Reviewed-by: greg <greg@noreply.kosmos.org>
2023-06-24 14:57:08 +00:00
b32e2fcb7b Merge pull request 'Fix docker volume mappings for node_modules, improve docker-compose usage' (#138) from bugfix/fix-docker-mapping into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #138
Reviewed-by: raucao <raucao@noreply.kosmos.org>
2023-06-23 15:38:30 +00:00
96a4db5bae improve sentence
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-06-23 17:32:39 +02:00
c7925f132e formatting
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-06-23 17:30:00 +02:00
e4406bf6ff use PRIMARY_DOMAIN for both web and sidekiq directives 2023-06-23 17:29:42 +02:00
ee7769c8c7 Update readme with simplified usage
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-06-23 17:21:52 +02:00
fdf3218f88 leave services uncommented, add /akkounts/node_modules to volume mapping as a directory to explicitly exclude 2023-06-23 17:21:43 +02:00
652ed5f7e3 copy files as list 2023-06-23 17:21:17 +02:00
Râu Cao
e4ed797920 Adjust specs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-06-22 13:57:55 +02:00
Râu Cao
93740f17ef Allow editing and resetting of all admin setting strings
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-06-22 13:48:29 +02:00
Râu Cao
affb058671 Add config for XMPP notifications from-address 2023-06-21 16:44:06 +02:00
716d4b944a Merge branch 'master' into feature/rs-oauth
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
# Conflicts:
#	app/models/user.rb
#	config/routes.rb
#	db/schema.rb
2023-06-20 14:07:46 +02:00
42af148168 Persist RS auth tokens in Redis 2023-06-20 14:02:48 +02:00
Râu Cao
dabd892a25 Improve RS OAuth UI
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-04-13 16:21:48 +02:00
Râu Cao
eeabbdb7df Merge branch 'master' into feature/rs-oauth
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-04-13 15:02:51 +02:00
ee42d68471 Add RemoteStorageAuthorization model
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-03-28 01:21:28 +02:00
7acc3b2106 RemoteStorage OAuth dialog 2023-03-28 01:21:28 +02:00
20c014607c Basic RemoteStorage settings 2023-03-27 22:52:01 +02:00
147 changed files with 4657 additions and 596 deletions

View File

@@ -17,9 +17,11 @@ steps:
branch:
- master
- name: rspec
image: guildeducation/rails:2.7.2-14.20.0
image: gitea.kosmos.org/kosmos/akkounts-ci:0.1.0
environment:
RAILS_ENV: test
REDIS_URL: redis://redis:6379/0
RS_REDIS_URL: redis://redis:6379/1
commands:
- bundle config unset deployment
- bundle config set cache_all 'true'
@@ -42,6 +44,10 @@ steps:
branch:
- master
services:
- name: redis
image: redis
volumes:
- name: cache
host:

View File

@@ -10,6 +10,14 @@ SMTP_DOMAIN=example.com
SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto
# S3_ENABLED=true
# S3_ENDPOINT=https://s3.kosmos.org
# S3_REGION=garage
# S3_BUCKET=akkounts-production
# S3_ALIAS_HOST=accounts.s3.kosmos.org
# S3_ACCESS_KEY=123456abcdefg
# S3_SECRET_KEY=123456789123456789123456789
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_ADMIN_PASSWORD=passthebutter
@@ -22,15 +30,20 @@ WEBHOOKS_ALLOWED_IPS='10.1.1.163'
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
DRONECI_PUBLIC_URL='https://drone.kosmos.org'
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
MASTODON_PUBLIC_URL='https://kosmos.social'
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
RS_STORAGE_URL='https://storage.kosmos.org'
RS_REDIS_URL='redis://localhost:6379/2'
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
BTCPAY_API_URL='http://localhost:23001/api/v1'
BTCPAY_STORE_ID=''
BTCPAY_AUTH_TOKEN=''
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

View File

@@ -1,16 +1,20 @@
PRIMARY_DOMAIN=kosmos.org
REDIS_URL='redis://localhost:6379/0'
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
BTCPAY_STORE_ID='123456'
DISCOURSE_PUBLIC_URL='http://discourse.example.com'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
EJABBERD_API_URL='http://xmpp.example.com/api'
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
LNDHUB_API_URL='http://localhost:3026'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
RS_STORAGE_URL='https://storage.kosmos.org'
RS_REDIS_URL='redis://localhost:6379/1'
WEBHOOKS_ALLOWED_IPS='10.1.1.23'

View File

@@ -7,6 +7,7 @@ version-resolver:
minor:
labels:
- 'release/minor'
- 'feature'
patch:
labels:
- 'release/patch'

2
.gitignore vendored
View File

@@ -23,6 +23,7 @@
!/tmp/pids/
!/tmp/pids/.keep
/storage
/public/assets
.byebug_history
@@ -39,6 +40,7 @@ yarn-debug.log*
# Ignore local dotenv config file
.env
.env.development
# Ignore redis dumps from sidekiq
dump.rdb

View File

@@ -4,14 +4,14 @@ FROM ruby:2.7.6
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
ldap-utils tini
ldap-utils tini libvips
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /akkounts
COPY Gemfile /akkounts/Gemfile
COPY Gemfile.lock /akkounts/Gemfile.lock
COPY package.json /akkounts/package.json
COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
RUN bundle install
RUN gem install foreman
RUN npm install -g yarn

View File

@@ -37,6 +37,7 @@ gem 'devise_ldap_authenticatable'
gem 'net-ldap'
# Utilities
gem "image_processing", "~> 1.12.2"
gem "rqrcode", "~> 2.0"
gem 'rails-settings-cached', '~> 2.8.3'
gem 'pagy', '~> 6.0', '>= 6.0.2'
@@ -46,6 +47,8 @@ gem 'flipper-ui'
# HTTP requests
gem 'faraday'
gem 'down'
gem 'aws-sdk-s3', require: false
# Background/scheduled jobs
gem 'sidekiq', '< 7'
@@ -58,12 +61,14 @@ gem "sentry-rails"
# Services
gem 'discourse_api'
gem "lnurl"
gem 'manifique', git: 'https://gitea.kosmos.org/5apps/manifique.git', branch: 'master'
gem 'nostr', git: 'https://gitea.kosmos.org/kosmos/nostr-gem.git', branch: 'feature/ruby_2.7_compat'
group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rspec-rails'
gem 'rails-controller-testing'
gem "byebug", "~> 11.1"
end

View File

@@ -1,3 +1,13 @@
GIT
remote: https://gitea.kosmos.org/5apps/manifique.git
revision: 8d79113438ee7c3e4288f840a135622519cffd5c
branch: master
specs:
manifique (0.1.0)
faraday (~> 2.7.11)
faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.15.4)
GIT
remote: https://gitea.kosmos.org/kosmos/nostr-gem.git
revision: 596529d9eb50d13b3f385245636698fccf37b442
@@ -14,82 +24,100 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
actionmailbox (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.5)
actionpack (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activesupport (= 7.0.5)
actionmailer (7.0.8)
actionpack (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activesupport (= 7.0.8)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.5)
actionview (= 7.0.5)
activesupport (= 7.0.5)
actionpack (7.0.8)
actionview (= 7.0.8)
activesupport (= 7.0.8)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.5)
actionpack (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
actiontext (7.0.8)
actionpack (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.5)
activesupport (= 7.0.5)
actionview (7.0.8)
activesupport (= 7.0.8)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.5)
activesupport (= 7.0.5)
activejob (7.0.8)
activesupport (= 7.0.8)
globalid (>= 0.3.6)
activemodel (7.0.5)
activesupport (= 7.0.5)
activerecord (7.0.5)
activemodel (= 7.0.5)
activesupport (= 7.0.5)
activestorage (7.0.5)
actionpack (= 7.0.5)
activejob (= 7.0.5)
activerecord (= 7.0.5)
activesupport (= 7.0.5)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activestorage (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activesupport (= 7.0.8)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.5)
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.4)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
aws-eventstream (1.2.0)
aws-partitions (1.839.0)
aws-sdk-core (3.185.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.72.0)
aws-sdk-core (~> 3, >= 3.184.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.136.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
backport (1.2.0)
bcrypt (3.1.18)
bech32 (1.3.0)
base64 (0.1.1)
bcrypt (3.1.19)
bech32 (1.4.2)
thor (>= 1.1.0)
benchmark (0.2.1)
bindex (0.8.1)
bip-schnorr (0.6.0)
ecdsa_ext (~> 0.5.0)
brow (0.4.1)
builder (3.2.4)
byebug (11.1.3)
capybara (3.39.2)
@@ -107,7 +135,7 @@ GEM
crack (0.4.5)
rexml
crass (1.0.6)
cssbundling-rails (1.1.2)
cssbundling-rails (1.3.3)
railties (>= 6.0.0)
database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3)
@@ -116,7 +144,7 @@ GEM
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.3)
devise (4.9.2)
devise (4.9.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@@ -135,6 +163,8 @@ GEM
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
railties (>= 3.2)
down (5.4.1)
addressable (~> 2.8)
e2mmap (0.1.0)
ecdsa (1.2.0)
ecdsa_ext (0.5.0)
@@ -149,9 +179,10 @@ GEM
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faker (3.2.0)
faker (3.2.1)
i18n (>= 1.8.11, < 2)
faraday (2.7.6)
faraday (2.7.11)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-follow_redirects (0.3.0)
@@ -159,41 +190,47 @@ GEM
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (3.0.2)
faye-websocket (0.11.2)
faye-websocket (0.11.3)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
ffi (1.15.5)
flipper (0.28.0)
ffi (1.16.3)
flipper (1.0.0)
brow (~> 0.4.1)
concurrent-ruby (< 2)
flipper-active_record (0.28.0)
flipper-active_record (1.0.0)
activerecord (>= 4.2, < 8)
flipper (~> 0.28.0)
flipper-ui (0.28.0)
flipper (~> 1.0.0)
flipper-ui (1.0.0)
erubi (>= 1.0.0, < 2.0.0)
flipper (~> 0.28.0)
rack (>= 1.4, < 3)
flipper (~> 1.0.0)
rack (>= 1.4, < 4)
rack-protection (>= 1.5.3, <= 4.0.0)
sanitize (< 7)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
globalid (1.1.0)
activesupport (>= 5.0)
globalid (1.2.1)
activesupport (>= 6.1)
hashdiff (1.0.1)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
importmap-rails (1.1.6)
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (1.2.1)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
jaro_winkler (1.5.6)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.6.2)
json (2.6.3)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.8.1)
@@ -206,10 +243,10 @@ GEM
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lnurl (1.0.1)
lnurl (1.1.0)
bech32 (~> 1.1)
lockbox (1.2.0)
loofah (2.21.3)
lockbox (1.3.0)
loofah (2.21.4)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -220,10 +257,11 @@ GEM
marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0)
mini_mime (1.1.2)
minitest (5.18.0)
mini_magick (4.12.0)
mini_mime (1.1.5)
minitest (5.20.0)
multipart-post (2.3.0)
net-imap (0.3.6)
net-imap (0.3.7)
date
net-protocol
net-ldap (0.18.0)
@@ -231,44 +269,51 @@ GEM
net-protocol
net-protocol (0.2.1)
timeout
net-smtp (0.3.3)
net-smtp (0.4.0)
net-protocol
nio4r (2.5.9)
nokogiri (1.15.2-x86_64-linux)
nokogiri (1.15.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
pagy (6.0.4)
pagy (6.1.0)
parallel (1.23.0)
parser (3.2.2.3)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
pg (1.2.3)
public_suffix (5.0.1)
public_suffix (5.0.3)
puma (4.3.12)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.7.1)
rack (2.2.7)
rack-protection (3.0.6)
rack
rack (2.2.8)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.5)
actioncable (= 7.0.5)
actionmailbox (= 7.0.5)
actionmailer (= 7.0.5)
actionpack (= 7.0.5)
actiontext (= 7.0.5)
actionview (= 7.0.5)
activejob (= 7.0.5)
activemodel (= 7.0.5)
activerecord (= 7.0.5)
activestorage (= 7.0.5)
activesupport (= 7.0.5)
rails (7.0.8)
actioncable (= 7.0.8)
actionmailbox (= 7.0.8)
actionmailer (= 7.0.8)
actionpack (= 7.0.8)
actiontext (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activemodel (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
bundler (>= 1.15.0)
railties (= 7.0.5)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
railties (= 7.0.8)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
@@ -276,9 +321,9 @@ GEM
rails-settings-cached (2.8.3)
activerecord (>= 5.0.0)
railties (>= 5.0.0)
railties (7.0.5)
actionpack (= 7.0.5)
activesupport (= 7.0.5)
railties (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -290,13 +335,13 @@ GEM
ffi (~> 1.0)
rbs (2.8.4)
redis (4.8.1)
regexp_parser (2.8.1)
responders (3.1.0)
regexp_parser (2.8.2)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5)
rexml (3.2.6)
rqrcode (2.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
@@ -306,7 +351,7 @@ GEM
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.5)
rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.0.3)
@@ -317,32 +362,36 @@ GEM
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-support (3.12.0)
rubocop (1.52.1)
rspec-support (3.12.1)
rubocop (1.57.1)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.3)
parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby-vips (2.2.0)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
sanitize (6.0.1)
sanitize (6.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
sentry-rails (5.9.0)
sentry-rails (5.12.0)
railties (>= 5.0)
sentry-ruby (~> 5.9.0)
sentry-ruby (5.9.0)
sentry-ruby (~> 5.12.0)
sentry-ruby (5.12.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.9)
sidekiq (6.5.12)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
@@ -366,53 +415,56 @@ GEM
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sprockets (4.2.0)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.6.3-x86_64-linux)
stimulus-rails (1.2.1)
sqlite3 (1.6.7-arm64-darwin)
sqlite3 (1.6.7-x86_64-linux)
stimulus-rails (1.3.0)
railties (>= 6.0.0)
thor (1.2.2)
tilt (2.2.0)
timeout (0.3.2)
turbo-rails (1.4.0)
thor (1.3.0)
tilt (2.3.0)
timeout (0.4.0)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
view_component (3.2.0)
unicode-display_width (2.5.0)
view_component (3.6.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.0)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.18.1)
webmock (3.19.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.5)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.34)
zeitwerk (2.6.8)
zeitwerk (2.6.12)
PLATFORMS
arm64-darwin-22
x86_64-linux
DEPENDENCIES
aws-sdk-s3
byebug (~> 11.1)
capybara
cssbundling-rails
@@ -421,12 +473,14 @@ DEPENDENCIES
devise_ldap_authenticatable
discourse_api
dotenv-rails
down
factory_bot_rails
faker
faraday
flipper
flipper-active_record
flipper-ui
image_processing (~> 1.12.2)
importmap-rails
jbuilder (~> 2.7)
letter_opener
@@ -434,12 +488,14 @@ DEPENDENCIES
listen (~> 3.2)
lnurl
lockbox
manifique!
net-ldap
nostr!
pagy (~> 6.0, >= 6.0.2)
pg (~> 1.2.3)
puma (~> 4.1)
rails (~> 7.0.2)
rails-controller-testing
rails-settings-cached (~> 2.8.3)
rqrcode (~> 2.0)
rspec-rails

View File

@@ -14,7 +14,6 @@ so:
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop)
2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml`
3. Run `docker compose up` and wait until 389ds announces its successful start
in the log output
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
@@ -53,12 +52,14 @@ Running all specs:
### Docker (Compose)
There is a working Docker Compose config file, which allows you to spin up both
There is a working Docker Compose config file, which define a number of services including
an app server for Rails as well as a local 389ds (LDAP) server.
By default, `docker-compose up` will only start the LDAP server, listening on
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
you want to use them.
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`,
listening on port 389 on your machine.
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at
the end of the docker compose command. eg. `docker compose up ldap redis`
#### LDAP server
@@ -106,6 +107,7 @@ command:
* [Tailwind CSS](https://tailwindcss.com/)
* [Sass](https://sass-lang.com/documentation)
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
* [Tailwind Stimulus Components](https://github.com/excid3/tailwindcss-stimulus-components)
### Testing

View File

@@ -2,6 +2,7 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@import "components/animations";
@import "components/base";
@import "components/buttons";
@import "components/dashboard_services";

View File

@@ -0,0 +1,16 @@
@keyframes scaleIn {
from {
transform: scale(0.5);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.animate-scale-in {
animation-name: scaleIn;
animation-duration: 0.15s;
animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1);
}

View File

@@ -14,12 +14,12 @@
@apply py-1 px-2 text-sm;
}
.btn-outline {
@apply border-2 border-gray-100 hover:bg-gray-100;
.btn-icon {
@apply py-2 px-3;
}
.btn-icon {
@apply px-3;
.btn-outline {
@apply py-2 border-2 border-gray-100 hover:bg-gray-100;
}
.btn-gray {

View File

@@ -0,0 +1,15 @@
<div class="flex">
<div class="<%= @icon_container_class %>">
<%= image_tag(@icon_path, class: 'h-full w-full') %>
</div>
<div class="flex-1 px-4">
<h4 class="sm:pt-2 mb-2 text-lg font-bold"><%= @name %></h4>
<p class="leading-snug"><%= @description %></p>
<p class="leading-snug flex flex-wrap gap-3">
<% @links.each do |link| %>
<a href="<%= link[1] %>" target="_blank"
class="flex-0 btn-sm btn-gray"><%= link[0] %></a>
<% end %>
</p>
</div>
</div>

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
class AppInfoComponent < ViewComponent::Base
def initialize(name:, description:, icon_path: , icon_fill_box: false, links: [])
@name = name
@description = description
@icon_path = icon_path
@icon_container_class = icon_container_class(icon_fill_box)
@links = links
end
def icon_container_class(icon_fill_box)
str = "flex-0 h-16 w-16 sm:h-28 sm:w-28 bg-white rounded-3xl overflow-hidden"
unless icon_fill_box
str += " p-2 border border-gray-200"
end
str
end
end

View File

@@ -1,4 +1,6 @@
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
<%= tag.public_send(@tag, class: "mb-6 last:mb-0", data: {
:'field-name' => @field_name
}) do %>
<% if @positioning == :vertical %>
<label class="block">
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
@@ -9,7 +11,21 @@
<%= @descripton %>
</p>
<% end %>
<%= content %>
<%= tag.p class: "flex gap-x-1", data: {
controller: @resettable ? "settings--resettable-field" : nil,
} do %>
<%= content %>
<% if @resettable %>
<button type="button"
class="relative grow-0 shrink-0 btn-md btn-outline text-red-700"
title="Reset to default value"
data-settings--resettable-field-target="resetButton"
data-action="settings--resettable-field#resetField">
Reset
</button>
<% end %>
<% end %>
</label>
<% elsif @positioning == :horizontal %>
<label class="block flex items-center justify-between">

View File

@@ -2,11 +2,15 @@
module FormElements
class FieldsetComponent < ViewComponent::Base
def initialize(tag: "li", positioning: :vertical, title:, description: nil)
def initialize(tag: "li", positioning: :vertical,
title:, description: nil,
field_name: nil, resettable: false)
@tag = tag
@positioning = positioning
@title = title
@descripton = description
@field_name = field_name
@resettable = resettable
end
end
end

View File

@@ -0,0 +1,13 @@
<%= render FormElements::FieldsetComponent.new(
title: @title,
description: @description,
field_name: "setting_#{@key.to_s}",
resettable: @resettable
) do %>
<%= method("#{@type}_field").call :setting, @key,
value: Setting.public_send(@key),
data: {
:'default-value' => Setting.get_field(@key)[:default]
},
class: "w-full" %>
<% end %>

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
module FormElements
class FieldsetResettableSettingComponent < ViewComponent::Base
def initialize(tag: "li", key:, type: :text, title:, description: nil)
@tag = tag
@positioning = :vertical
@title = title
@description = description
@key = key.to_sym
@type = type
@resettable = is_resettable?(@key)
end
def is_resettable?(key)
default_value = Setting.get_field(key)[:default]
default_value.present? && (default_value != Setting.send(key))
end
end
end

View File

@@ -0,0 +1,28 @@
<div tabindex="-1" class="relative z-10">
<!-- Modal Background -->
<div class="hidden fixed inset-0 bg-black bg-opacity-80 overflow-y-auto flex items-center justify-center"
data-modal-target="background"
data-action="click->modal#closeBackground"
data-transition-enter="transition-all ease-in-out duration-100"
data-transition-enter-from="bg-opacity-0"
data-transition-enter-to="bg-opacity-80"
data-transition-leave="transition-all ease-in-out duration-100"
data-transition-leave-from="bg-opacity-80"
data-transition-leave-to="bg-opacity-0">
<!-- Modal Container -->
<div data-modal-target="container"
class="max-h-screen w-auto max-w-lg relative
hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center">
<!-- Modal Card -->
<div class="m-1 bg-white rounded shadow">
<div class="p-8">
<%= content %>
<div class="flex justify-end items-center flex-wrap mt-6">
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,2 @@
class ModalComponent < ViewComponent::Base
end

View File

@@ -0,0 +1,6 @@
<%= render ModalComponent.new do %>
<% if @descripton.present? %>
<p class="mb-6"><%= @description %></p>
<% end %>
<p><%= raw @qr_code_svg %></p>
<% end %>

View File

@@ -0,0 +1,24 @@
require "rqrcode"
class QrCodeModalComponent < ViewComponent::Base
def initialize(qr_content:, description: nil)
@description = description
@qr_code_svg = qr_code_svg(qr_content)
end
private
def qr_code_svg(content)
qr_code = RQRCode::QRCode.new(content)
qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
end
end

View File

@@ -0,0 +1,25 @@
<div class="py-4 w-1/2 flex items-center gap-6">
<div class="h-16 w-16 flex-none">
<%= image_tag s3_image_url(@web_app.icon), class: "h-full w-full" %>
</div>
<div class="flex-grow">
<h4 class="mb-1 text-lg font-bold">
<%= @web_app.name %>
</h4>
<p class="text-sm text-gray-500">
<%= @auth.client_id %>
</p>
</div>
<!-- <div> -->
<!-- <p class="text&#45;sm text&#45;gray&#45;500"> -->
<!-- Approved <%= time_ago_in_words @auth.created_at %> ago -->
<!-- </p> -->
<!-- </div> -->
<div>
<p class="text-sm text-gray-500">
<%= link_to "#", class: "btn-md btn-outline text-red-700 relative" do %>
Revoke access
<% end %>
</p>
</div>
</div>

View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
class RsAuthComponent < ViewComponent::Base
def initialize(auth:)
@auth = auth
@web_app = auth.web_app
end
end

View File

@@ -1,4 +1,8 @@
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
<% if @icon.present? %>
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
<% elsif @text_icon.present? %>
<span class="mr-3"><%= @text_icon %></span>
<% end %>
<span class="truncate"><%= @name %></span>
<% end %>

View File

@@ -1,11 +1,13 @@
# frozen_string_literal: true
class SidenavLinkComponent < ViewComponent::Base
def initialize(name:, level: 1, path:, icon:, active: false, disabled: false)
def initialize(name:, level: 1, path:, icon: nil, text_icon: nil,
active: false, disabled: false)
@name = name
@level = level
@path = path
@icon = icon
@text_icon = text_icon
@active = active
@disabled = disabled
@link_class = class_names_link(path)

View File

@@ -0,0 +1,9 @@
class Admin::AppCatalog::WebAppsController < Admin::AppCatalogController
def index
@pagy, @web_apps = pagy(AppCatalog::WebApp.order('created_at desc'))
@stats = {
known_apps: AppCatalog::WebApp.count
}
end
end

View File

@@ -0,0 +1,9 @@
class Admin::AppCatalogController < Admin::BaseController
before_action :set_current_section
private
def set_current_section
@current_section = :app_catalog
end
end

View File

@@ -3,7 +3,7 @@ class Admin::Settings::ServicesController < Admin::SettingsController
@service = params[:s]
if @service.blank?
redirect_to admin_settings_services_path(params: { s: "discourse" })
redirect_to admin_settings_services_path(params: { s: "btcpay" })
end
end

View File

@@ -20,6 +20,8 @@ class Admin::UsersController < Admin::BaseController
end
@services_enabled = @user.services_enabled
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn, ou: @user.ou)
end
private

View File

@@ -0,0 +1,29 @@
class Api::BtcpayController < Api::BaseController
before_action :require_feature_enabled
def onchain_btc_balance
balance = BtcpayManager::FetchOnchainWalletBalance.call
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
def lightning_btc_balance
balance = BtcpayManager::FetchLightningWalletBalance.call
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
private
def require_feature_enabled
unless Setting.btcpay_publish_wallet_balances
http_status :not_found and return
end
end
end

View File

@@ -1,13 +0,0 @@
class Api::KreditsController < Api::BaseController
def onchain_btc_balance
btcpay = BtcPay.new
balance = btcpay.onchain_wallet_balance
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
end

View File

@@ -37,4 +37,8 @@ class ApplicationController < ActionController::Base
format.any { head status }
end
end
def after_sign_in_path_for(user)
session[:user_return_to] || root_path
end
end

View File

@@ -0,0 +1,146 @@
class Rs::OauthController < ApplicationController
before_action :require_signed_in_with_username, only: :new
before_action :authenticate_user!, only: :create
def new
username, org = params[:useraddress].split("@")
@user = User.where(cn: username.downcase, ou: org).first
@scopes = parse_scopes params[:scope]
@redirect_uri = params[:redirect_uri]
@client_id = params[:client_id]
@state = params[:state]
@root_access_requested = (@scopes & [":r",":rw"]).any?
@denial_url = url_with_state("#{@redirect_uri}#error=access_denied", @state)
@expire_at_dates = [["Never", nil],
["In 1 month", 1.month.from_now],
["In 1 day", 1.day.from_now]]
http_status :bad_request and return unless @redirect_uri.present?
unless current_user == @user
sign_out :user
redirect_to new_rs_oauth_url(@user.address,
scope: params[:scope],
redirect_uri: params[:redirect_uri],
client_id: params[:client_id],
state: params[:state])
return
end
unless @client_id.present?
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_request", @state),
allow_other_host: true) and return
end
if @scopes.empty?
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_scope", @state),
allow_other_host: true) and return
end
unless hostname_of(@client_id) == hostname_of(@redirect_uri)
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_client", @state),
allow_other_host: true) and return
end
@client_id.gsub!(/http(s)?:\/\//, "")
if auth = current_user.remote_storage_authorizations.valid.where(permissions: @scopes, client_id: @client_id).first
redirect_to(url_with_state("#{@redirect_uri}#access_token=#{auth.token}", @state),
allow_other_host: true) and return
end
end
def create
unless current_user.id.to_s == params[:user_id]
Rails.logger.info("NO MATCH: #{params[:user_id]}, #{current_user.id}")
http_status :forbidden and return
end
permissions = parse_scopes params[:scope]
redirect_uri = params[:redirect_uri].presence
client_id = params[:client_id].presence
state = params[:state].presence
expire_at = params[:expire_at].presence
http_status :bad_request and return unless redirect_uri.present?
if permissions.empty?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
allow_other_host: true) and return
end
unless client_id.present?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_request", state),
allow_other_host: true) and return
end
unless hostname_of(client_id) == hostname_of(redirect_uri)
redirect_to(url_with_state("#{redirect_uri}#error=invalid_client", state),
allow_other_host: true) and return
end
client_id.gsub!(/http(s)?:\/\//, "")
auth = current_user.remote_storage_authorizations.create!(
permissions: permissions,
client_id: client_id,
redirect_uri: redirect_uri,
app_name: client_id, #TODO use user-defined name
expire_at: expire_at
)
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state),
allow_other_host: true
end
# GET /rs/oauth/token/:id/launch_app
def launch_app
auth = current_user.remote_storage_authorizations.find(params[:id])
redirect_to app_auth_url(auth), allow_other_host: true
end
private
def require_signed_in_with_username
unless user_signed_in?
username, org = params[:useraddress].split("@")
session[:user_return_to] = request.url
redirect_to new_user_session_path(cn: username, ou: org)
end
end
def app_auth_url(auth)
url = "#{auth.url}#remotestorage=#{current_user.address}"
url += "&access_token=#{auth.token}"
url
end
def hostname_of(uri)
uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0]
end
def parse_scopes(scope_string)
return [] if scope_string.blank?
scopes = scope_string.
gsub(/\[|\]/, "").
gsub(/\,/, " ").
gsub(/\/:/, ":").
split(/\s/).map(&:strip).
reject(&:empty?)
scopes = [":r"] if scopes.include?("*:r")
scopes = [":rw"] if scopes.include?("*:rw")
scopes
end
def url_with_state(url, state)
state ? "#{url}&state=#{CGI.escape(state)}" : url
end
end

View File

@@ -0,0 +1,9 @@
class Services::BaseController < ApplicationController
before_action :set_current_section
private
def set_current_section
@current_section = :services
end
end

View File

@@ -0,0 +1,14 @@
class Services::ChatController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.services_enabled.include?(:xmpp)
end
private
def require_service_available
http_status :not_found unless Setting.ejabberd_enabled?
end
end

View File

@@ -8,8 +8,7 @@ class Services::LightningController < ApplicationController
before_action :fetch_balance
def index
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
initialize_lndhub_qr_code
@wallet_setup_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
end
def transactions
@@ -56,20 +55,6 @@ class Services::LightningController < ApplicationController
private
def initialize_lndhub_qr_code
qr_code = RQRCode::QRCode.new(@wallet_url)
@lndhub_qr_svg = qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
end
def authenticate_with_lndhub(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]

View File

@@ -0,0 +1,14 @@
class Services::MastodonController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.services_enabled.include?(:mastodon)
end
private
def require_service_available
http_status :not_found unless Setting.mastodon_enabled?
end
end

View File

@@ -1,13 +1,13 @@
class Services::RemotestorageController < ApplicationController
before_action :require_user_signed_in
before_action :require_service_enabled
class Services::RemotestorageController < Services::BaseController
before_action :authenticate_user!
before_action :require_feature_enabled
before_action :set_current_section
before_action :require_service_available
def dashboard
# unless current_user.services_enabled.include?(:remotestorage)
# redirect_to service_remotestorage_info_path
# end
@rs_auths = current_user.remote_storage_authorizations
end
private
@@ -18,13 +18,7 @@ class Services::RemotestorageController < ApplicationController
end
end
def require_service_enabled
unless Setting.remotestorage_enabled?
http_status :not_found
end
end
def set_current_section
@current_section = :services
def require_service_available
http_status :not_found unless Setting.remotestorage_enabled?
end
end

View File

@@ -19,10 +19,15 @@ class SettingsController < ApplicationController
def update
@user.preferences.merge!(user_params[:preferences] || {})
@user.display_name = user_params[:display_name]
@user.avatar_new = user_params[:avatar]
if @user.save
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
LdapManager::UpdateDisplayName.call(@user.dn, user_params[:display_name])
LdapManager::UpdateDisplayName.call(@user.dn, @user.display_name)
end
if @user.avatar_new.present?
LdapManager::UpdateAvatar.call(@user.dn, @user.avatar_new)
end
redirect_to setting_path(@settings_section), flash: {
@@ -117,7 +122,7 @@ class SettingsController < ApplicationController
end
def user_params
params.require(:user).permit(:display_name, preferences: [
params.require(:user).permit(:display_name, :avatar, preferences: [
:lightning_notify_sats_received,
:xmpp_exchange_contacts_with_invitees
])

View File

@@ -30,7 +30,7 @@ class WebhooksController < ApplicationController
def notify_xmpp(address, amt_sats, memo)
payload = {
type: "normal",
from: Setting.primary_domain,
from: Setting.xmpp_notifications_from_address,
to: address,
subject: "Sats received!",
body: "#{helpers.number_with_delimiter amt_sats} sats received in your Lightning wallet:\n> #{memo}"

View File

@@ -0,0 +1,11 @@
module OauthHelper
def scope_name(scope)
scope.gsub(/(\:.+)/, '')
end
def scope_permissions(scope)
scope.match(/\:r$/) ? "r" : "rw"
end
end

View File

@@ -1,7 +1,11 @@
import { Application } from "@hotwired/stimulus"
import { Modal, Tabs } from "tailwindcss-stimulus-components"
const application = Application.start()
application.register('modal', Modal)
application.register('tabs', Tabs)
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application

View File

@@ -0,0 +1,10 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "resetButton" ]
resetField () {
const inputEl = this.element.querySelector('input')
inputEl.value = inputEl.dataset.defaultValue
}
}

View File

@@ -0,0 +1,10 @@
class RemoteStorageExpireAuthorizationJob < ApplicationJob
queue_as :remotestorage
def perform(rs_auth_id)
rs_auth = RemoteStorageAuthorization.find rs_auth_id
return unless rs_auth.expire_at.nil? || rs_auth.expire_at <= DateTime.now
rs_auth.destroy!
end
end

View File

@@ -0,0 +1,5 @@
module AppCatalog
def self.table_name_prefix
"app_catalog_"
end
end

View File

@@ -0,0 +1,16 @@
class AppCatalog::WebApp < ApplicationRecord
store :metadata, coder: JSON
has_many :remote_storage_authorizations
has_one_attached :icon
has_one_attached :apple_touch_icon
validates :url, presence: true, uniqueness: true
validates :url, format: { with: URI.regexp },
if: Proc.new { |a| a.url.present? }
def update_metadata
AppCatalogManager::UpdateMetadata.call(self)
end
end

View File

@@ -0,0 +1,81 @@
class RemoteStorageAuthorization < ApplicationRecord
belongs_to :user
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
serialize :permissions
validates_presence_of :permissions
validates_presence_of :client_id
scope :valid, -> { where(expire_at: nil).or(where(expire_at: (DateTime.now)..)) }
scope :expired, -> { where(expire_at: ..(DateTime.now)) }
after_initialize do |a|
a.permissions = [] if a.permissions == nil
end
before_create :generate_token
before_create :store_token_in_redis
before_create :find_or_create_web_app
after_create :schedule_token_expiry
# after_create :notify_user
before_destroy :delete_token_from_redis
after_destroy :remove_token_expiry_job
def url
# TODO use web app scope in addition to host
uri = URI.parse self.redirect_uri
"#{uri.scheme}://#{client_id}"
end
def delete_token_from_redis
key = "rs:authorizations:#{user.address}:#{token}"
redis.srem? key, redis.smembers(key)
end
private
def redis
@redis ||= Redis.new(url: Setting.rs_redis_url)
end
def generate_token(length=16)
self.token = SecureRandom.hex(length) if self.token.blank?
end
def store_token_in_redis
redis.sadd "rs:authorizations:#{user.address}:#{token}", permissions
end
def schedule_token_expiry
return unless expire_at.present?
RemoteStorageExpireAuthorizationJob.set(wait_until: expire_at)
.perform_later(id)
end
def remove_token_expiry_job
queue = Sidekiq::Queue.new(RemoteStorageExpireAuthorizationJob.queue_name)
queue.each do |job|
next unless job.display_class == "RemoteStorageExpireAuthorizationJob"
job.delete if job.display_args == [id]
end
end
def find_or_create_web_app
if looks_like_hosted_origin?
web_app = AppCatalog::WebApp.find_or_create_by!(url: self.url)
web_app.update_metadata unless web_app.name.present?
self.web_app = web_app
self.app_name = web_app.name.presence || client_id
else
self.app_name = client_id
end
end
def looks_like_hosted_origin?
uri = URI.parse self.redirect_uri
!!(uri.host =~ /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/)
rescue URI::InvalidURIError
false
end
end

View File

@@ -12,7 +12,7 @@ class Setting < RailsSettings::Base
# Internal services
#
field :redis_url, type: :string, readonly: true,
field :redis_url, type: :string,
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
#
@@ -29,38 +29,67 @@ class Setting < RailsSettings::Base
field :xmpp_default_rooms, type: :array, default: []
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
field :xmpp_notifications_from_address, type: :string, default: primary_domain
#
# Sentry
#
field :sentry_enabled, type: :boolean, readonly: true,
default: (ENV["SENTRY_DSN"].present?.to_s || false)
default: ENV["SENTRY_DSN"].present?
#
# BTCPay Server
#
field :btcpay_api_url, type: :string,
default: ENV["BTCPAY_API_URL"].presence
field :btcpay_enabled, type: :boolean,
default: ENV["BTCPAY_API_URL"].present?
field :btcpay_store_id, type: :string,
default: ENV["BTCPAY_STORE_ID"].presence
field :btcpay_auth_token, type: :string,
default: ENV["BTCPAY_AUTH_TOKEN"].presence
field :btcpay_publish_wallet_balances, type: :boolean, default: true
#
# Discourse
#
field :discourse_public_url, type: :string, readonly: true,
field :discourse_public_url, type: :string,
default: ENV["DISCOURSE_PUBLIC_URL"].presence
field :discourse_enabled, type: :boolean,
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
default: ENV["DISCOURSE_PUBLIC_URL"].present?
field :discourse_connect_secret, type: :string, readonly: true,
field :discourse_connect_secret, type: :string,
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
#
# Drone CI
#
field :droneci_public_url, type: :string,
default: ENV["DRONECI_PUBLIC_URL"].presence
field :droneci_enabled, type: :boolean,
default: ENV["DRONECI_PUBLIC_URL"].present?
#
# ejabberd
#
field :ejabberd_enabled, type: :boolean,
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
default: ENV["EJABBERD_API_URL"].present?
field :ejabberd_api_url, type: :string, readonly: true,
field :ejabberd_api_url, type: :string,
default: ENV["EJABBERD_API_URL"].presence
field :ejabberd_admin_url, type: :string, readonly: true,
field :ejabberd_admin_url, type: :string,
default: ENV["EJABBERD_ADMIN_URL"].presence
field :ejabberd_buddy_roster, type: :string,
@@ -70,50 +99,56 @@ class Setting < RailsSettings::Base
# Gitea
#
field :gitea_public_url, type: :string, readonly: true,
field :gitea_public_url, type: :string,
default: ENV["GITEA_PUBLIC_URL"].presence
field :gitea_enabled, type: :boolean,
default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false)
default: ENV["GITEA_PUBLIC_URL"].present?
#
# Lightning Network
#
field :lndhub_api_url, type: :string, readonly: true,
field :lndhub_api_url, type: :string,
default: ENV["LNDHUB_API_URL"].presence
field :lndhub_enabled, type: :boolean,
default: (ENV["LNDHUB_API_URL"].present?.to_s || false)
default: ENV["LNDHUB_API_URL"].present?
field :lndhub_admin_token, type: :string,
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
field :lndhub_admin_enabled, type: :boolean,
default: (ENV["LNDHUB_ADMIN_UI"] || false)
default: ENV["LNDHUB_ADMIN_UI"] || false
field :lndhub_public_key, type: :string, readonly: true,
field :lndhub_public_key, type: :string,
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
field :lndhub_keysend_enabled, type: :boolean,
default: -> { self.lndhub_public_key.present?.to_s || false }
default: -> { self.lndhub_public_key.present? }
#
# Mastodon
#
field :mastodon_public_url, type: :string, readonly: true,
field :mastodon_public_url, type: :string,
default: ENV["MASTODON_PUBLIC_URL"].presence
field :mastodon_enabled, type: :boolean,
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
default: ENV["MASTODON_PUBLIC_URL"].present?
field :mastodon_address_domain, type: :string,
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
#
# MediaWiki
#
field :mediawiki_public_url, type: :string, readonly: true,
field :mediawiki_public_url, type: :string,
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
field :mediawiki_enabled, type: :boolean,
default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false)
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
#
# Nostr
@@ -126,8 +161,11 @@ class Setting < RailsSettings::Base
#
field :remotestorage_enabled, type: :boolean,
default: (ENV["RS_STORAGE_URL"].present?.to_s || false)
default: ENV["RS_STORAGE_URL"].present?
field :rs_storage_url, type: :string,
default: ENV["RS_STORAGE_URL"].presence
field :rs_redis_url, type: :string,
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
end

View File

@@ -2,10 +2,14 @@ class User < ApplicationRecord
include EmailValidatable
attr_accessor :display_name
attr_accessor :avatar_new
serialize :preferences, UserPreferences
#
# Relations
#
has_many :invitations, dependent: :destroy
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
has_one :inviter, through: :invitation, source: :user
@@ -18,6 +22,12 @@ class User < ApplicationRecord
has_many :accounts, through: :lndhub_user
has_many :remote_storage_authorizations
#
# Validations
#
validates_uniqueness_of :cn, scope: :ou
validates_length_of :cn, minimum: 3
validates_format_of :cn, with: /\A([a-z0-9\-])*\z/,
@@ -38,10 +48,20 @@ class User < ApplicationRecord
validates_uniqueness_of :nostr_pubkey, allow_blank: true
validate :acceptable_avatar
#
# Scopes
#
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :pending, -> { where(confirmed_at: nil) }
scope :all_except, -> (user) { where.not(id: user) }
#
# Encrypted database columns
#
has_encrypted :ln_login, :ln_password
# Include default devise modules. Others available are:
@@ -70,6 +90,7 @@ class User < ApplicationRecord
# E-Mail update confirmed
LdapManager::UpdateEmail.call(self.dn, self.email)
else
# TODO Make configurable
# E-Mail from signup confirmed (i.e. account activation)
enable_service %w[ discourse gitea mediawiki xmpp ]
@@ -107,6 +128,11 @@ class User < ApplicationRecord
"#{self.cn}@#{self.ou}"
end
def mastodon_address
return nil unless Setting.mastodon_enabled?
"#{self.cn}@#{Setting.mastodon_address_domain}"
end
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
@@ -132,6 +158,10 @@ class User < ApplicationRecord
@display_name ||= ldap_entry[:display_name]
end
def avatar
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn, ou: ou)
end
def services_enabled
ldap_entry[:service] || []
end
@@ -160,4 +190,17 @@ class User < ApplicationRecord
return @ldap_service if defined?(@ldap_service)
@ldap_service = LdapService.new
end
def acceptable_avatar
return unless avatar_new.present?
if avatar_new.size > 1.megabyte
errors.add(:avatar, "file size is too large")
end
acceptable_types = ["image/jpeg", "image/png"]
unless acceptable_types.include?(avatar_new.content_type)
errors.add(:avatar, "must be a JPEG or PNG file")
end
end
end

View File

@@ -0,0 +1,49 @@
require "manifique"
require "down"
module AppCatalogManager
class UpdateMetadata < AppCatalogManagerService
def initialize(app)
@app = app
end
def call
agent = Manifique::Agent.new(url: @app.url)
metadata = agent.fetch_metadata
@app.name = metadata.name
[:name, :short_name, :description, :theme_color, :background_color,
:display, :start_url, :scope, :share_target, :icons].each do |prop|
@app.metadata[prop] = metadata.send(prop) if prop
end
if icon = metadata.select_icon(sizes: "256x256")
attach_remote_image(:icon, icon)
end
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
attach_remote_image(:apple_touch_icon, apple_touch_icon)
end
@app.save!
rescue Manifique::Error => e
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
Rails.logger.warn(msg)
Sentry.capture_message(msg) if Setting.sentry_enabled?
false
end
def attach_remote_image(attachment_name, icon)
if icon['src'].start_with?("http")
download_url = icon['src']
else
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
end
filename = "#{attachment_name}.png"
key = "web_apps/#{@app.id}/icons/#{attachment_name}.png"
tempfile = Down.download(download_url)
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
end
end
end

View File

@@ -0,0 +1,2 @@
class AppCatalogManagerService < ApplicationService
end

View File

@@ -1,32 +0,0 @@
#
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
#
class BtcPay
def initialize
@base_url = ENV["BTCPAY_API_URL"]
@store_id = Rails.application.credentials.btcpay[:store_id]
@auth_token = Rails.application.credentials.btcpay[:auth_token]
end
def onchain_wallet_balance
res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet"
{
balance: res["balance"].to_f,
unconfirmed_balance: res["unconfirmedBalance"].to_f,
confirmed_balance: res["confirmedBalance"].to_f
}
end
private
def get(endpoint)
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "token #{@auth_token}"
})
JSON.parse(res.body)
end
end

View File

@@ -0,0 +1,11 @@
module BtcpayManager
class FetchLightningWalletBalance < BtcpayManagerService
def call
res = get "stores/#{store_id}/lightning/BTC/balance"
{
balance: res["offchain"]["local"].to_i / 1000 # msats to sats
}
end
end
end

View File

@@ -0,0 +1,13 @@
module BtcpayManager
class FetchOnchainWalletBalance < BtcpayManagerService
def call
res = get "stores/#{store_id}/payment-methods/onchain/BTC/wallet"
{
balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats
unconfirmed_balance: (res["unconfirmedBalance"].to_f * 100000000).to_i,
confirmed_balance: (res["confirmedBalance"].to_f * 100000000).to_i
}
end
end
end

View File

@@ -0,0 +1,24 @@
#
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
#
class BtcpayManagerService < ApplicationService
attr_reader :base_url, :store_id, :auth_token
def initialize
@base_url = Setting.btcpay_api_url
@store_id = Setting.btcpay_store_id
@auth_token = Setting.btcpay_auth_token
end
private
def get(endpoint)
res = Faraday.get("#{base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "token #{auth_token}"
})
JSON.parse(res.body)
end
end

View File

@@ -0,0 +1,17 @@
module LdapManager
class FetchAvatar < LdapManagerService
def initialize(cn:, ou: nil)
@cn = cn
@ou = ou
end
def call
treebase = @ou ? "ou=#{@ou},cn=users,#{suffix}" : ldap_config["base"]
attributes = %w{ jpegPhoto }
filter = Net::LDAP::Filter.eq("cn", @cn)
entry = ldap_client.search(base: treebase, filter: filter, attributes: attributes).first
entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
end
end
end

View File

@@ -0,0 +1,27 @@
require "image_processing/vips"
module LdapManager
class UpdateAvatar < LdapManagerService
def initialize(dn, file)
@dn = dn
@img_data = process(file)
end
def call
replace_attribute @dn, :jpegPhoto, @img_data
end
private
def process(file)
processed = ImageProcessing::Vips
.resize_to_fill(512, 512)
.source(file)
.convert("jpeg")
.saver(strip: true)
.call
Base64.strict_encode64 processed.read
end
end
end

View File

@@ -1,2 +1,5 @@
class LdapManagerService < LdapService
def suffix
@suffix ||= ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
end
end

View File

@@ -14,7 +14,7 @@ class LndhubV2 < Lndhub
end
def create_account(payload={})
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
post "v2/users", payload, admin_token: Setting.lndhub_admin_token
end
def create_invoice(payload)

View File

@@ -0,0 +1,52 @@
<%= render HeaderComponent.new(title: "App Catalog") %>
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_app_catalog') do %>
<section>
<%= render QuickstatsContainerComponent.new do %>
<%= render QuickstatsItemComponent.new(
type: :number,
title: 'Known Web Apps',
value: @stats[:known_apps],
) %>
<%# <%= render QuickstatsItemComponent.new(
<%# type: :number,
<%# title: 'Accepted',
<%# value: @stats[:accepted],
<%# ) %>
<%# <%= render QuickstatsItemComponent.new(
<%# type: :number,
<%# title: 'Users with referrals',
<%# value: @stats[:users_with_referrals],
<%# meta: "/ #{User.count}"
<%# ) %>
<% end %>
</section>
<% if @web_apps.any? %>
<section>
<h3>Web Apps</h3>
<table class="divided mb-8">
<thead>
<tr>
<th>Name</th>
<th>URL</th>
<th class="hidden md:table-cell">RS Auths</th>
<th class="hidden md:table-cell">Created at</th>
</tr>
</thead>
<tbody>
<% @web_apps.each do |web_app| %>
<tr>
<td><%= web_app.name %></td>
<td><%= link_to web_app.url, web_app.url,
target: "_blank", rel: "nofollow noopener",
class: "ks-text-link" %></td>
<td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td>
<td class="hidden md:table-cell"><%= web_app.created_at %></td>
</tr>
<% end %>
</tbody>
</table>
<%== pagy_nav @pagy %>
</section>
<% end %>
<% end %>

View File

@@ -0,0 +1,37 @@
<h3>BTCPay Server</h3>
<ul role="list">
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :btcpay_enabled,
enabled: Setting.btcpay_enabled?,
title: "Enable BTCPay integration",
description: "BTCPay configuration present and features enabled"
) %>
<% if Setting.btcpay_enabled? %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :btcpay_api_url,
title: "API URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :btcpay_store_id,
title: "Store ID"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :btcpay_auth_token,
type: :password,
title: "Auth Token"
) %>
</ul>
</section>
<section>
<h3>REST API</h3>
<ul role="list">
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :btcpay_publish_wallet_balances,
enabled: Setting.btcpay_publish_wallet_balances?,
title: "Publish wallet balances",
description: "Publish the store's on-chain and Lightning wallet balances"
) %>
<% end %>
</ul>

View File

@@ -8,16 +8,15 @@
description: "Discourse configuration present and features enabled"
) %>
<% if Setting.discourse_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
<%= f.text_field :discourse_public_url,
value: Setting.discourse_public_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetComponent.new(title: "Connect secret") do %>
<%= f.password_field :discourse_connect_secret,
value: Setting.discourse_connect_secret,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :discourse_public_url,
title: "Public URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :discourse_connect_secret,
type: :password,
title: "Connect secret"
) %>
<% end %>
</ul>
<% if Setting.discourse_enabled? %>
@@ -31,14 +30,14 @@
<input type="text" class="grow" disabled="disabled"
value="https://<%= Setting.accounts_domain %>/discourse/connect"
data-clipboard-target="source" />
<button class="btn-md btn-icon btn-blue shrink-0"
<button class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
</li>

View File

@@ -0,0 +1,16 @@
<h3>Drone CI</h3>
<ul role="list">
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :droneci_enabled,
enabled: Setting.droneci_enabled?,
title: "Enable Drone CI integration",
description: "Drone CI configuration present and features enabled"
) %>
<% if Setting.droneci_enabled? %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :droneci_public_url,
title: "Public URL"
) %>
<% end %>
</ul>

View File

@@ -8,16 +8,14 @@
description: "ejabberd configuration present and features enabled"
) %>
<% if Setting.ejabberd_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
<%= f.text_field :ejabberd_api_url,
value: Setting.ejabberd_api_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetComponent.new(title: "Admin URL") do %>
<%= f.text_field :ejabberd_admin_url,
value: Setting.ejabberd_admin_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :ejabberd_api_url,
title: "API URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :ejabberd_admin_url,
title: "Admin URL"
) %>
</ul>
<h3 class="mt-10">User default settings</h3>
<ul role="list">
@@ -37,12 +35,24 @@
title: "Auto-join default rooms",
description: "Automatically join above default rooms in chat clients"
) %>
<%= render FormElements::FieldsetComponent.new(
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :ejabberd_buddy_roster,
title: "Contact roster name",
description: "Used when exchanging contacts after signup from invitation"
) %>
</ul>
<h3 class="mt-10">Notifications</h3>
<ul role="list">
<%= render FormElements::FieldsetComponent.new(
title: "From address",
description: "Address (JID) of the account notifications are sent from",
resettable: Setting.get_field(:xmpp_notifications_from_address)[:default] != Setting.xmpp_notifications_from_address
) do %>
<%= f.text_field :ejabberd_buddy_roster,
value: Setting.ejabberd_buddy_roster,
<%= f.text_field :xmpp_notifications_from_address,
value: Setting.xmpp_notifications_from_address,
data: {
:'default-value' => Setting.get_field(:xmpp_notifications_from_address)[:default]
},
class: "w-full" %>
<% end %>
<% end %>

View File

@@ -8,10 +8,9 @@
description: "Gitea configuration present and features enabled"
) %>
<% if Setting.gitea_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
<%= f.text_field :gitea_public_url,
value: Setting.gitea_public_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :gitea_public_url,
title: "Public URL"
) %>
<% end %>
</ul>

View File

@@ -8,31 +8,36 @@
description: "LNDHub configuration present and wallet features enabled"
) %>
<% if Setting.lndhub_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
<%= f.text_field :lndhub_api_url,
value: Setting.lndhub_api_url,
class: "w-full", disabled: true %>
<% end %>
<% end %>
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :lndhub_admin_enabled,
enabled: Setting.lndhub_admin_enabled?,
title: "Enable LNDHub admin panel",
description: "LNDHub database configuration present and admin panel enabled"
) %>
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :lndhub_keysend_enabled,
enabled: Setting.lndhub_keysend_enabled?,
title: "Enable keysend payments",
description: "Allow users to receive invoice-less payments to their Lightning Address"
) %>
<% if Setting.lndhub_keysend_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Public key", description: "The public key of the Lightning node used by LNDHub") do %>
<%= f.text_field :lndhub_public_key,
value: Setting.lndhub_public_key,
class: "w-full", disabled: true %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :lndhub_api_url,
title: "API URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :lndhub_admin_token,
type: :password,
title: "Admin token",
description: "Auth token for creating new lndhub accounts"
) %>
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :lndhub_admin_enabled,
enabled: Setting.lndhub_admin_enabled?,
title: "Enable LNDHub admin panel",
description: "LNDHub database configuration present and admin panel enabled"
) %>
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
attribute: :lndhub_keysend_enabled,
enabled: Setting.lndhub_keysend_enabled?,
title: "Enable keysend payments",
description: "Allow users to receive invoice-less payments to their Lightning Address"
) %>
<% if Setting.lndhub_keysend_enabled? %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :lndhub_public_key,
title: "Public key",
description: "The public key of the Lightning node used by LNDHub"
) %>
<% end %>
<% end %>
</ul>

View File

@@ -8,10 +8,13 @@
description: "Mastodon configuration present and features enabled"
) %>
<% if Setting.mastodon_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
<%= f.text_field :mastodon_public_url,
value: Setting.mastodon_public_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :mastodon_public_url,
title: "Public URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :mastodon_address_domain,
title: "User address domain"
) %>
<% end %>
</ul>

View File

@@ -8,10 +8,9 @@
description: "MediaWiki configuration present and features enabled"
) %>
<% if Setting.mediawiki_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
<%= f.text_field :mediawiki_public_url,
value: Setting.mediawiki_public_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :mediawiki_public_url,
title: "Public URL"
) %>
<% end %>
</ul>

View File

@@ -1,4 +1,5 @@
<h3>RemoteStorage</h3>
<p class="text-red-600 mb-8">Feature currently in development.</p>
<ul role="list">
<%= render FormElements::FieldsetToggleComponent.new(
form: f,
@@ -8,10 +9,13 @@
description: "RemoteStorage configuration present and features enabled"
) %>
<% if Setting.remotestorage_enabled? %>
<%= render FormElements::FieldsetComponent.new(title: "Storage URL") do %>
<%= f.text_field :rs_storage_url,
value: Setting.rs_storage_url,
class: "w-full", disabled: true %>
<% end %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :rs_storage_url,
title: "Storage Base URL"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :rs_redis_url,
title: "Redis URL"
) %>
<% end %>
</ul>

View File

@@ -63,6 +63,12 @@
</section>
<section class="sm:flex-1 sm:pt-0">
<% if @avatar.present? %>
<h3>LDAP<h3>
<p>
<img src="data:image/jpeg;base64,<%= @avatar %>" class="h-48 w-48" />
</p>
<% end %>
<!-- <h3>Actions</h3> -->
</section>
</div>

View File

@@ -7,73 +7,85 @@
services:
</p>
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to "https://wiki.kosmos.org/Services:Chat",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600">
Federated chat rooms and instant messaging
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:95%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600">
Kosmos community forums and user support/help site
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to "https://wiki.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600">
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to "https://gitea.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600">
Code hosting and collaboration for software projects
</p>
<% end %>
</div>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-70px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to "https://drone.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600">
Continuous integration for software projects on Gitea
</p>
<% end %>
</div>
<% if Setting.remotestorage_enabled? && Flipper.enabled?(:remotestorage, current_user) %>
<% if Setting.ejabberd_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to services_chat_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600">
Federated chat rooms and instant messaging
</p>
<% end %>
</div>
<% end %>
<% if Setting.mastodon_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:80%] bg-[right_top_-30px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Mastodon</h3>
<p class="text-gray-600">
Your account on the Open Social Web
</p>
<% end %>
</div>
<% end %>
<% if Setting.discourse_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:95%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600">
Kosmos community forums and user support/help site
</p>
<% end %>
</div>
<% end %>
<% if Setting.lndhub_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network
</p>
<% end %>
</div>
<% end %>
<% if Setting.gitea_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to Setting.gitea_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600">
Code hosting and collaboration for software projects
</p>
<% end %>
</div>
<% end %>
<% if Setting.droneci_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-70px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to Setting.droneci_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600">
Continuous integration for software projects on Gitea
</p>
<% end %>
</div>
<% end %>
<% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %>
<div class="border border-gray-300 rounded-md hover:border-gray-400">
<%= link_to services_storage_path,
class: "block h-full px-6 py-6 rounded-md" do %>
@@ -84,16 +96,19 @@
<% end %>
</div>
<% end %>
<!-- <div class="border border&#45;gray&#45;300 rounded&#45;md hover:border&#45;gray&#45;400 -->
<!-- bg&#45;[length:80%] bg&#45;[right_top_&#45;30px] bg&#45;no&#45;repeat -->
<!-- bg&#45;[url(/img/logos/icon_mastodon.svg)]"> -->
<!-- <%= link_to "https://kosmos.social", class: "block h&#45;full px&#45;6 py&#45;6 rounded&#45;md" do %> -->
<!-- <h3 class="mb&#45;3.5">Mastodon</h3> -->
<!-- <p class="text&#45;gray&#45;400"> -->
<!-- Your account on the Open Social Web -->
<!-- </p> -->
<!-- <% end %> -->
<!-- </div> -->
<% if Setting.mediawiki_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to Setting.mediawiki_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600">
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
<% end %>
</div>
</section>
<% end %>

View File

@@ -12,7 +12,8 @@
<div class="mb-6">
<%= f.label :cn, 'User', class: 'block mb-2 font-bold' %>
<p class="flex gap-2 items-center">
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
<%= f.text_field :cn, value: h(params[:cn]),
autofocus: params[:cn].blank?, autocomplete: "username",
required: true, class: "relative grow", tabindex: "1" %>
<span class="relative shrink-0 text-gray-500">@ <%= Setting.primary_domain %></span>
</p>
@@ -20,7 +21,8 @@
<p class="mb-8">
<%= f.label :password, class: 'block mb-2 font-bold' %>
<%= f.password_field :password, autocomplete: "current-password",
required: true, class: "w-full", tabindex: "2" %>
autofocus: params[:cn].present?, required: true,
class: "w-full", tabindex: "2" %>
</p>
<%= tag.div class: "flex items-center mb-8 gap-x-3", data: {

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle <%= custom_class %>"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

Before

Width:  |  Height:  |  Size: 424 B

After

Width:  |  Height:  |  Size: 445 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512" fill="currentColor" stroke="currentColor" stroke-width="2" class="<%= custom_class %>"><path d="M475.31 364.144L288 256l187.31-108.144c5.74-3.314 7.706-10.653 4.392-16.392l-4-6.928c-3.314-5.74-10.653-7.706-16.392-4.392L272 228.287V12c0-6.627-5.373-12-12-12h-8c-6.627 0-12 5.373-12 12v216.287L52.69 120.144c-5.74-3.314-13.079-1.347-16.392 4.392l-4 6.928c-3.314 5.74-1.347 13.079 4.392 16.392L224 256 36.69 364.144c-5.74 3.314-7.706 10.653-4.392 16.392l4 6.928c3.314 5.74 10.653 7.706 16.392 4.392L240 283.713V500c0 6.627 5.373 12 12 12h8c6.627 0 12-5.373 12-12V283.713l187.31 108.143c5.74 3.314 13.079 1.347 16.392-4.392l4-6.928c3.314-5.74 1.347-13.079-4.392-16.392z"/></svg>

After

Width:  |  Height:  |  Size: 760 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder <%= custom_class %>"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1,11 @@
<svg class="icon-qr-code <%= custom_class %>" fill="currentColor" width="90" height="90" version="1.1" viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg">
<path id="path2" d="m22.014 22.612c0-2.5389 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0587 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
<path id="path4" d="m22.014 61.598c0-2.539 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0586 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
<path id="path6" d="m50 22.612c0-2.5389 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0587 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
<path id="path8" d="m50 61.598c0-2.539 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0586 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
<path id="path10" d="m8.85 45c0-1.7397 1.4103-3.15 3.15-3.15h66.5c1.7397 0 3.15 1.4103 3.15 3.15s-1.4103 3.15-3.15 3.15h-66.5c-1.7397 0-3.15-1.4103-3.15-3.15z" clip-rule="evenodd" fill-rule="evenodd"/>
<path id="path12" d="m11.566 0c-6.3876 0-11.566 5.1782-11.566 11.566v14.627c0 1.7713 1.4359 3.2073 3.2072 3.2073s3.2072-1.436 3.2072-3.2073v-14.627c0-2.845 2.3064-5.1514 5.1514-5.1514h14.627c1.7713 0 3.2073-1.4359 3.2073-3.2072s-1.436-3.2072-3.2073-3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
<path id="path14" d="m11.566 90c-6.3876 0-11.566-5.1782-11.566-11.566v-14.628c0-1.7713 1.4359-3.2072 3.2072-3.2072s3.2072 1.4359 3.2072 3.2072v14.628c0 2.845 2.3064 5.1513 5.1514 5.1513h14.627c1.7713 0 3.2073 1.436 3.2073 3.2073 0 1.7712-1.436 3.2072-3.2073 3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
<path id="path16" d="m78.434 0c6.3876 0 11.566 5.1782 11.566 11.566v14.627c0 1.7713-1.4359 3.2073-3.2072 3.2073s-3.2072-1.436-3.2072-3.2073v-14.627c0-2.845-2.3064-5.1514-5.1514-5.1514h-14.627c-1.7713 0-3.2073-1.4359-3.2073-3.2072s1.436-3.2072 3.2073-3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
<path id="path18" d="m78.434 90c6.3876 0 11.566-5.1782 11.566-11.566v-14.628c0-1.7713-1.4359-3.2072-3.2072-3.2072s-3.2072 1.4359-3.2072 3.2072v14.628c0 2.845-2.3064 5.1513-5.1514 5.1513h-14.627c-1.7713 0-3.2073 1.436-3.2073 3.2073 0 1.7712 1.436 3.2072 3.2073 3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star <%= custom_class %>"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -8,20 +8,27 @@
</p>
<ul class="md:w-3/4">
<% @invitations_unused.each do |invitation| %>
<li class="font-mono mb-2 flex gap-1" data-controller="clipboard">
<input type="text" disabled class="relative grow"
<li class="mb-3 flex gap-1"
data-controller="clipboard modal"
data-action="keydown.esc->modal#close">
<input type="text" disabled class="relative grow font-mono"
value="<%= invitation_url(invitation.token) %>"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0 w-auto"
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-action="click->modal#open" title="Show QR code">
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</button>
<%= render QrCodeModalComponent.new(qr_content: invitation_url(invitation.token)) %>
</li>
<% end %>
</ul>

View File

@@ -0,0 +1,58 @@
<%= render HeaderCompactComponent.new(title: "Storage") %>
<%= render MainCompactComponent.new do %>
<section class="permissions">
<p class="mb-8">
The app on
<%= link_to @client_id, "https://#{@client_id}", class: "ks-text-link" %>
is asking for access to these folders:
</p>
<% if @root_access_requested %>
<p class="scope text-lg">
<span class="text-red-700">
<%= render partial: "icons/alert-triangle",
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>
All files and directories
</span>
<% if (@scopes & [":r"]).any? %>
<span class="text-sm text-gray-500">(read only)</span>
<% end %>
</p>
<% else %>
<% @scopes.each do |scope| %>
<p class="scope text-gray-600">
<span class="text-lg">
<%= render partial: "icons/folder",
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>
<%= scope_name(scope) %>
</span>
<% if scope_permissions(scope) == "r" %>
<span>(read only)</span>
<% end %>
</p>
<% end %>
<% end %>
<%= form_with(url: rs_oauth_path, method: :post, data: { turbo: false }) do |f| %>
<%= f.hidden_field :redirect_uri, value: @redirect_uri %>
<%= f.hidden_field :scope, value: @scopes.join(" ") %>
<%= f.hidden_field :user_id, value: @user.id %>
<%= f.hidden_field :client_id, value: @client_id %>
<%= f.hidden_field :state, value: @state %>
<p class="mt-8 mb-6">
<%= f.label :expire_at, "Permission expires:", class: "mr-1.5" %>
<%= f.select :expire_at, options_for_select(@expire_at_dates) %>
</p>
<p class="text-sm text-gray-500">
You can revoke access for this app at any time on your storage dashboard.
</p>
<p class="mt-8 flex flex-col sm:flex-row gap-3 sm:gap-2 sm:justify-items-stretch">
<%= f.submit "Allow",
class: "btn-md btn-blue w-full sm:order-last sm:grow",
data: { disable_with: "Saving..." } %>
<%= link_to "Deny", @denial_url, class: "btn-md btn-gray text-red-700 w-full sm:grow" %>
</div>
<% end %>
</section>
<% end %>

View File

@@ -0,0 +1,199 @@
<%= render HeaderComponent.new(title: "Chat") %>
<%= render MainSimpleComponent.new do %>
<section>
<p class="mb-6">
Chat with anyone on the open Jabber (XMPP) network. Message people directly, or
join public channels or private rooms.
</p>
</section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Your Chat Address</h3>
<p class="mb-6">
When you exchange contacts with people, give them your
address, or add them using their address:
</p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
<input type="text" id="user_address" class="grow"
value=<%= current_user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-action="click->modal#open" title="Show QR code">
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</button>
</p>
<%= render QrCodeModalComponent.new(qr_content: "xmpp:"+current_user.address) %>
</section>
<section>
<h3>Chat Apps</h3>
<p>
Use your account with many different apps, and on any devices you wish!
When opening an app for the first time, just enter your user address and
password to log in.
</p>
</section>
<section>
<h3>Recommended Apps</h3>
<div data-controller="tabs"
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
class="mb-12">
<select data-action="tabs#change" data-tabs-target="select"
class="block w-full mb-8 sm:hidden">
<optgroup label="Mobile">
<option>Android</option>
<option>iOS</option>
</optgroup>
<optgroup label="Desktop">
<option>Linux</option>
<option>Windows</option>
<option>macOS</option>
</optgroup>
</select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Android
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS
</a>
</li>
<!-- <li class="mr&#45;2" data&#45;tabs&#45;target="tab" data&#45;action="click&#45;>tabs#change"> -->
<!-- <a href="#" class="bg&#45;white inline&#45;block py&#45;2 px&#45;4 font&#45;semibold no&#45;underline"> -->
<!-- Web -->
<!-- </a> -->
<!-- </li> -->
</ul>
<div id="apps-android" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Conversations",
description: "The gold standard for Jabber on mobile devices",
icon_path: "/img/logos/icon_conversations.png",
links: [
["Website", "https://conversations.im"],
["Google Play", "https://play.google.com/store/apps/details?id=eu.siacs.conversations"],
["F-Droid", "https://f-droid.org/en/packages/eu.siacs.conversations/"],
]
) %>
</div>
<div id="apps-ios" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Siskin IM",
description: "Lightweight and powerful chat app for iPhone and iPad",
icon_path: "/img/logos/logo_siskin.png",
links: [
["Website", "https://siskin.im"],
["App Store", "https://apps.apple.com/us/app/tigase-messenger/id1153516838"]
]
) %>
<%= render AppInfoComponent.new(
name: "Monal",
description: "A chat app for iOS, iPadOS, and macOS",
icon_path: "/img/logos/icon_monal.svg",
icon_fill_box: true,
links: [
["Website", "https://monal-im.org"],
["App Store", "https://apps.apple.com/app/id317711500"]
]
) %>
</div>
<div id="apps-linux" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Dino",
description: "A modern and simple chat app for Linux (good for GNOME)",
icon_path: "/img/logos/icon_dino.svg",
links: [
["Website", "https://dino.im"],
["Install from package", "https://github.com/dino/dino/wiki/Distribution-Packages"]
]
) %>
<%= render AppInfoComponent.new(
name: "Kaidan",
description: "A fairly new, user-friendly chat app for all devices (good for KDE)",
icon_path: "/img/logos/icon_kaidan.svg",
links: [
["Website", "https://kaidan.im"],
]
) %>
<%= render AppInfoComponent.new(
name: "Gajim",
description: "A fully-featured chat app for Linux and Windows",
icon_path: "/img/logos/icon_gajim.png",
links: [
["Website", "https://gajim.org/"]
]
) %>
</div>
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Gajim",
description: "A fully-featured chat app for Linux and Windows",
icon_path: "/img/logos/icon_gajim.png",
links: [
["Website", "https://gajim.org/"],
["Microsoft Store", "https://apps.microsoft.com/store/detail/9PGGF6HD43F9?launch=true&mode=mini"],
["Download options", "https://gajim.org/download/"]
]
) %>
</div>
<div id="apps-mac" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Beagle IM",
description: "Lightweight and powerful chat app for macOS",
icon_path: "/img/logos/logo_beagle.png",
links: [
["Website", "https://beagle.im"],
["App Store", "https://apps.apple.com/us/app/beagleim-by-tigase-inc/id1445349494"]
]
) %>
<%= render AppInfoComponent.new(
name: "Monal",
description: "A chat app for iOS, iPadOS, and macOS",
icon_path: "/img/logos/icon_monal.svg",
icon_fill_box: true,
links: [
["Website", "https://monal-im.org"],
["App Store", "https://apps.apple.com/app/id1637078500"]
]
) %>
</div>
<!-- <div class="hidden grid grid&#45;cols&#45;1 gap&#45;4 sm:gap&#45;6" data&#45;tabs&#45;target="panel"> -->
<!-- Web -->
<!-- </div> -->
</div>
</section>
<% end %>

View File

@@ -16,20 +16,20 @@
<input type="text" id="user_address" class="grow"
value=<%= current_user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
</p>
</section>
<section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Wallet Apps</h3>
<p>
You can connect various wallet apps to your Kosmos account. This allows
@@ -40,19 +40,16 @@
</p>
<p data-controller="clipboard" class="my-6 text-center md:text-left">
<input type="text" disabled class="hidden" aria-hidden=true
value="<%= @wallet_url%>" data-clipboard-target="source" />
value="<%= @wallet_setup_url %>" data-clipboard-target="source" />
<button id="copy-setup-code" class="btn-md btn-blue w-full sm:w-auto"
data-action="clipboard#copy" data-clipboard-target="trigger">
<span class="content-initial">Copy setup code/URL</span>
<span class="content-active hidden">Copied ✔</span>
</button>
<span class="mx-2 my-2 md:my-0 block md:inline">or</span>
<button id="show-setup-code" class="btn-md btn-blue w-full sm:w-auto">Show setup QR code</button>
<button id="hide-setup-code" class="hidden btn-md btn-blue w-full sm:w-auto">Hide setup QR code</button>
</p>
<p id="setup-code" class="hidden my-10 w-full text-center">
<%= raw @lndhub_qr_svg %>
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">Show setup QR code</button>
</p>
<%= render QrCodeModalComponent.new(qr_content: @wallet_setup_url) %>
</section>
<section>
@@ -119,27 +116,3 @@
</p>
</section>
<% end %>
<script type="text/javascript">
(function () {
const buttonShow = document.querySelector('#show-setup-code');
const buttonHide = document.querySelector('#hide-setup-code');
const setupCode = document.querySelector('#setup-code');
buttonShow.addEventListener('click', function(ev) {
ev.preventDefault();
setupCode.classList.remove('hidden');
buttonHide.classList.remove('hidden');
buttonShow.classList.add('hidden');
setupCode.scrollIntoView({behavior: "smooth", block: "nearest"});
});
buttonHide.addEventListener('click', function(ev) {
ev.preventDefault();
const el = document.querySelector('#setup-code');
setupCode.classList.add('hidden');
buttonHide.classList.add('hidden');
buttonShow.classList.remove('hidden');
});
})();
</script>

View File

@@ -0,0 +1,219 @@
<%= render HeaderComponent.new(title: "Social") %>
<%= render MainSimpleComponent.new do %>
<section>
<p class="mb-6">
Follow and interact with anyone on the open social web, from your Kosmos Mastodon account.
</p>
</section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Your User Address</h3>
<p class="mb-6">
Others can follow you under this address:
</p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
<input type="text" id="user_address" class="grow"
value=<%= current_user.mastodon_address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
data-action="click->modal#open" title="Show QR code">
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</button>
</p>
<%= render QrCodeModalComponent.new(qr_content: current_user.address) %>
</section>
<section>
<h3>Social Apps</h3>
<p>
Use your Mastodon account with many different apps, and on any devices
you wish! When adding your account to an app, you will log in via
<a href="https://kosmos.social" target="_blank" class="ks-text-link">kosmos.social</a>.
</p>
</section>
<section>
<h3>Recommended Apps</h3>
<div data-controller="tabs"
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
class="mb-12">
<select data-action="tabs#change" data-tabs-target="select"
class="block w-full mb-8 sm:hidden">
<option>Web</option>
<optgroup label="Mobile">
<option>Android</option>
<option>iOS</option>
</optgroup>
<optgroup label="Desktop">
<option>Linux</option>
<option>Windows</option>
<option>macOS</option>
</optgroup>
</select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Web
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-5 font-semibold no-underline">
Android
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows
</a>
</li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS
</a>
</li>
</ul>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "kosmos.social",
description: "The official Web app",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Launch", "https://kosmos.social"]
]
) %>
<%= render AppInfoComponent.new(
name: "Elk",
description: " A nimble Mastodon web client",
icon_path: "/img/logos/icon_elk.svg",
links: [
["Launch", "https://elk.zone"],
["GitHub", "https://github.com/elk-zone/elk"]
]
) %>
<%= render AppInfoComponent.new(
name: "Sengi",
description: "A cross-platform app, inspired by TweetDeck",
icon_path: "/img/logos/icon_sengi.png",
links: [
["Website", "https://nicolasconstant.github.io/sengi/"],
["GitHub", "https://github.com/NicolasConstant/sengi"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastodon for Android",
description: "Android client by the Mastodon core team",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Website", "https://joinmastodon.org/apps"],
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android"]
]
) %>
<%= render AppInfoComponent.new(
name: "Fedilab",
description: "Android client with many features",
icon_path: "/img/logos/icon_fedilab.png",
links: [
["Website", "https://fedilab.app"],
["Google Play", "https://play.google.com/store/apps/details?id=app.fedilab.android"],
["F-Droid", "https://f-droid.org/packages/fr.gouv.etalab.mastodon"],
]
) %>
<%= render AppInfoComponent.new(
name: "Megalodon",
description: "A popular fork of the official Android app",
icon_path: "/img/logos/icon_megalodon.png",
icon_fill_box: true,
links: [
["Website", "https://sk22.github.io/megalodon/"],
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastodon for iOS",
description: "iOS client by the Mastodon core team",
icon_path: "/img/logos/icon_mastodon-2.svg",
links: [
["Website", "https://joinmastodon.org/apps"],
["App Store", "https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974"]
]
) %>
<%= render AppInfoComponent.new(
name: "Ice Cubes",
description: "Slick, fast, open source, and with customizable UI",
icon_path: "/img/logos/icon_icecubes.png",
icon_fill_box: true,
links: [
["App Store", "https://apps.apple.com/us/app/ice-cubes-for-mastodon/id6444915884"],
["GitHub", "https://github.com/Dimillian/IceCubesApp"]
]
) %>
<%= render AppInfoComponent.new(
name: "Mammoth",
description: " Powerful, fast, feature-rich",
icon_path: "/img/logos/icon_mammoth.png",
links: [
["Website", "https://getmammoth.app/"],
["App Store", "https://apps.apple.com/app/mammoth-for-mastodon/id1667573899"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Tuba",
description: "A simple, fast Mastodon app for Linux (good on GNOME)",
icon_path: "/img/logos/icon_tuba.svg",
links: [
["Website", "https://tuba.geopjr.dev"],
["Flathub", "https://flathub.org/apps/dev.geopjr.Tuba"],
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Sengi",
description: "A cross-platform app, inspired by TweetDeck",
icon_path: "/img/logos/icon_sengi.png",
links: [
["Website", "https://nicolasconstant.github.io/sengi/"],
["GitHub", "https://github.com/NicolasConstant/sengi"]
]
) %>
</div>
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
<%= render AppInfoComponent.new(
name: "Mastonaut",
description: "Simple, elegant, and native Mastodon client for Mac",
icon_path: "/img/logos/icon_mastonaut.png",
links: [
["Launch", "https://www.mastonaut.app"],
["Mac App Store", "https://apps.apple.com/app/mastonaut/id1450757574"]
]
) %>
</div>
</div>
</section>
<% end %>

View File

@@ -2,6 +2,15 @@
<%= render MainSimpleComponent.new do %>
<section>
<h3>Feature enabled</h3>
<h3 class="">Connected Apps</h3>
<% if @rs_auths.any? %>
<div class="w-full grid grid-columns-1 divide-y">
<% @rs_auths.each do |auth| %>
<%= render RsAuthComponent.new(auth: auth) %>
<% end %>
</div>
<% else %>
<p>No apps connected yet.</p>
<% end %>
</section>
<% end %>

View File

@@ -13,13 +13,13 @@
'settings--account--email-target': 'emailField'
}, required: true %>
<button type="button" id="edit-email"
class="btn-md btn-icon btn-blue shrink-0 hidden initial-visible"
class="btn-md btn-icon btn-outline shrink-0 hidden initial-visible"
data-settings--account--email-target="editEmailButton"
data-action="settings--account--email#editEmail"
title="Edit email address">
<span class="">
<%= render partial: "icons/edit-3", locals: {
custom_class: "text-white h-4 w-4 inline" } %>
custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
</p>

View File

@@ -1,33 +1,62 @@
<section>
<h3>Profile</h3>
<p class="mb-2">
<%= label :user_address, 'User address', class: 'font-bold' %>
</p>
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
<input type="text" id="user_address" class="grow"
value=<%= @user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
</span>
</button>
</p>
<p class="text-sm text-gray-500">
Your user address for Chat and Lightning Network.
</p>
<div class="mb-6">
<p class="mb-2">
<%= label :user_address, 'User address', class: 'font-bold' %>
</p>
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
<input type="text" id="user_address" class="grow"
value=<%= @user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
data-clipboard-target="trigger" data-action="clipboard#copy"
title="Copy to clipboard">
<span class="content-initial">
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
</span>
</button>
</p>
<p class="text-sm text-gray-500">
Your user address for Chat and Lightning Network.
</p>
</div>
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
<%= f.text_field :display_name, class: "w-full sm:w-3/5 mb-2" %>
<%= f.text_field :display_name, class: "w-full sm:w-3/5" %>
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
<p class="error-msg"><%= @validation_errors[:display_name].first %></p>
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
<% end %>
<% end %>
<label class="block">
<p class="font-bold mb-1">
Avatar
</p>
<p class="text-gray-500">
Default profile picture
</p>
<div class="flex items-center gap-6">
<% if current_user.avatar.present? %>
<p class="flex-none">
<%= image_tag "data:image/jpeg;base64,#{current_user.avatar}", class: "h-24 w-24 rounded-lg" %>
</p>
<% end %>
<div class="grow">
<p class="mb-2">
<%= f.file_field :avatar, class: "" %>
<p class="text-sm text-gray-500">
JPEG or PNG image, not larger than 1 megabyte
</p>
<% if @validation_errors.present? && @validation_errors[:avatar].present? %>
<p class="error-msg mb-2"><%= @validation_errors[:avatar].first %></p>
<% end %>
</div>
</div>
</label>
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
</p>

View File

@@ -10,5 +10,9 @@
<%= link_to "Lightning", admin_lightning_path,
class: main_nav_class(@current_section, :lightning) %>
<% end %>
<% if Setting.remotestorage_enabled? %>
<%= link_to "Apps", admin_app_catalog_web_apps_path,
class: main_nav_class(@current_section, :app_catalog) %>
<% end %>
<%= link_to "Settings", admin_settings_registrations_path,
class: main_nav_class(@current_section, :settings) %>

View File

@@ -0,0 +1,10 @@
<%= render SidenavLinkComponent.new(
name: "Web Apps", path: admin_app_catalog_web_apps_path, icon: "globe",
active: current_page?(admin_app_catalog_web_apps_path)
) %>
<%= render SidenavLinkComponent.new(
name: "Recommended Apps", path: "#", icon: "star", disabled: true
) %>
<%= render SidenavLinkComponent.new(
name: "OAuth Apps", path: "#", icon: "key", disabled: true
) %>

View File

@@ -1,56 +1,70 @@
<%= render SidenavLinkComponent.new(
level: 2,
name: "BTCPay",
path: admin_settings_services_path(params: { s: "btcpay" }),
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "btcpay" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Discourse",
path: admin_settings_services_path(params: { s: "discourse" }),
icon: Setting.discourse_enabled? ? "check" : "x",
text_icon: Setting.discourse_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Drone CI",
path: admin_settings_services_path(params: { s: "droneci" }),
text_icon: Setting.droneci_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "ejabberd",
path: admin_settings_services_path(params: { s: "ejabberd" }),
icon: Setting.ejabberd_enabled? ? "check" : "x",
text_icon: Setting.ejabberd_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Gitea",
path: admin_settings_services_path(params: { s: "gitea" }),
icon: Setting.gitea_enabled? ? "check" : "x",
text_icon: Setting.gitea_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "gitea" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "LNDHub",
path: admin_settings_services_path(params: { s: "lndhub" }),
icon: Setting.lndhub_enabled? ? "check" : "x",
text_icon: Setting.lndhub_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "lndhub" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Mastodon",
path: admin_settings_services_path(params: { s: "mastodon" }),
icon: Setting.mastodon_enabled? ? "check" : "x",
text_icon: Setting.mastodon_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "mastodon" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "MediaWiki",
path: admin_settings_services_path(params: { s: "mediawiki" }),
icon: Setting.mediawiki_enabled? ? "check" : "x",
text_icon: Setting.mediawiki_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Nostr",
path: admin_settings_services_path(params: { s: "nostr" }),
icon: Setting.nostr_enabled? ? "check" : "x",
text_icon: Setting.nostr_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "nostr" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "RemoteStorage",
path: admin_settings_services_path(params: { s: "remotestorage" }),
icon: Setting.remotestorage_enabled? ? "check" : "x",
text_icon: Setting.remotestorage_enabled? ? "" : "",
active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })),
) %>

View File

@@ -0,0 +1,6 @@
<%= render HeaderCompactComponent.new(title: "404") %>
<%= render MainCompactComponent.new do %>
<h2>Bad request</h2>
<p>Please go back and try again.</p>
<% end %>

4
ci/Dockerfile Normal file
View File

@@ -0,0 +1,4 @@
# syntax=docker/dockerfile:1
FROM guildeducation/rails:2.7.2-14.20.0
RUN apt-get update -qq && apt-get install -y --no-install-recommends ldap-utils libvips

View File

@@ -5,7 +5,7 @@ require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_mailbox/engine"

View File

@@ -1 +1 @@
yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg==
tmI5vm7qZhaigr52jEBVWkRdj+EE+9OmPh3vWXC7kA/OHuuucpr7SodychuMkQDPLM0BLk88LFsqvRIR+mqnLWpRC+P9aeUFE6ohxSWzcAd7Y4sgxUD8zpCRPndrwTw0hxXXj1WZSYeWn4BoAB34aV+gYen2MajZF3a95hJGtS5yjgWxvLVkQQKqRDfykkfX6fCS0BPo5X7sT7m4xwCATD/D4219wajm5W3TIdkriHtwt28ZLspaRWA5e0UkzKf8+/Gaj2CrW7UWcvew8R93zQ5RA2/Sp3sDTVN+kLz9I9Q095lQC0ywCAEFYHeKmc2tjrzqRaAAWu06xmWLqGIg21G+A/UU9lUJOkIpxQACWoOfS2IoXR1nXhgXMopkz3aCBXDxKw554v4H2QyOceOsuRf2C685ibMqzQkKMmJ4tcbiOJL77DUc08JTjB8Dq4Ohr8sMzXbV/hATevjYoRP0XarLekqhLv90ZLuIVY16DwB0CzACeNBKeKbeLqJF51upRRWgi+gTbYpV04yUwnXdyssF8mydWocgihrTryBi8F6PsuhBGcaYdP+0yibnGxDCC4x2rupbBfMj2OIX7pYzgtIHB3Eo954Y+bCoggqbE/Qrb9VVXNMgtKgLt8EGWU2tg6wl9QicitIq87uLDAade93zTn6rmcKPywjMDo6jbVIs653ZdUhiKdHGdpnJccbgQ/iLSPB1umNnCeaEX5jM+K9zBvl7ZMCdSk1YIQ==--ekKumqLiSlVJNwMe--K/ecXmmMT1x+WnIXMbHBDw==

View File

@@ -70,4 +70,10 @@ Rails.application.configure do
# Allow requests from any IP
config.web_console.whiny_requests = false
if ENV["S3_ENABLED"]
config.active_storage.service = :s3
else
config.active_storage.service = :local
end
end

View File

@@ -110,6 +110,12 @@ Rails.application.configure do
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true
if ENV["S3_ENABLED"]
config.active_storage.service = :s3
else
config.active_storage.service = :local
end
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true

View File

@@ -51,4 +51,11 @@ Rails.application.configure do
}
config.active_job.queue_adapter = :test
if ENV["S3_ENABLED"]
config.active_storage.service = :s3
else
# Store attachments on the local disk (in ./tmp)
config.active_storage.service = :test
end
end

View File

@@ -6,3 +6,4 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "bech32" # @2.0.0
pin "tailwindcss-stimulus-components" # @4.0.3

View File

@@ -21,12 +21,16 @@ Rails.application.routes.draw do
namespace :services do
get 'storage', to: 'remotestorage#dashboard'
resource :chat, only: [:show], controller: 'chat'
resources :lightning, only: [:index] do
collection do
get 'transactions'
get 'qr_lnurlp'
end
end
resource :mastodon, only: [:show], controller: 'mastodon'
end
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
@@ -50,23 +54,36 @@ Rails.application.routes.draw do
post 'webhooks/lndhub', to: 'webhooks#lndhub'
namespace :api do
get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance'
get 'btcpay/onchain_btc_balance', to: 'btcpay#onchain_btc_balance'
get 'btcpay/lightning_btc_balance', to: 'btcpay#lightning_btc_balance'
end
namespace :admin do
root to: 'dashboard#index'
resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
get 'invitations', to: 'invitations#index'
resources :donations
get 'lightning', to: 'lightning#index'
namespace :app_catalog do
resources 'web_apps', only: ['index']
end
namespace :settings do
resources 'registrations', only: ['index', 'create']
resources 'services', only: ['index', 'create']
end
end
get ".well-known/webfinger", to: 'webfinger#show'
namespace :rs do
resource :oauth, only: [:new, :create], path_names: {
new: ':useraddress', create: ':useraddress'
}, controller: 'oauth', constraints: { useraddress: /[^\/]+/}
get 'oauth/token/:id/launch_app' => 'oauth#launch_app', as: :launch_app
end
get '.well-known/webfinger', to: 'webfinger#show'
namespace :discourse do
get "connect", to: 'sso#connect'
@@ -83,4 +100,8 @@ Rails.application.routes.draw do
end
root to: 'dashboard#index'
direct :s3_image do |blob|
File.join(ENV['S3_ALIAS_HOST'], blob.key)
end
end

View File

@@ -1,7 +1,17 @@
local:
service: Disk
root: <%= Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
<% if ENV["S3_ENABLED"] %>
s3:
service: S3
endpoint: <%= ENV["S3_ENDPOINT"] %>
region: <%= ENV["S3_REGION"] %>
bucket: <%= ENV["S3_BUCKET"] %>
access_key_id: <%= ENV["S3_ACCESS_KEY"] %>
secret_access_key: <%= ENV["S3_SECRET_KEY"] %>
<% end %>

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