Compare commits
32 Commits
v0.5.0
...
7f5b8c22b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f5b8c22b7
|
||
|
|
44ec856091
|
||
|
|
6ac9f68566
|
||
|
|
69bd9dcf78
|
||
|
|
2c6e6caab6
|
||
|
|
a551e8d2ef
|
||
|
|
9166d887c4
|
||
|
|
d3313a202b
|
||
|
|
029e6b011d
|
||
|
|
f1ef257c97
|
||
|
|
3f1c4f17a7
|
||
|
|
8c5852fefe
|
||
|
|
317dba0b2d
|
||
|
|
ab08c9ecf5
|
||
|
|
49dd7bd96d
|
||
|
|
c650b73ff9
|
||
|
|
ea6173c60f
|
||
|
|
cd46daa6ba
|
||
|
|
df3f91e2d0
|
||
|
|
7c4106d7a2
|
||
|
|
cb79884c53
|
||
|
|
9266fdcfc2
|
||
|
|
b04d822586
|
||
|
|
ba7b10fbc8
|
||
|
|
3b51670850
|
||
|
|
5b2a9e8c0c
|
||
|
|
ae239d584f
|
||
|
|
e6d65ee582
|
||
|
|
08b3ec499e
|
||
|
|
6c17fbbbeb
|
||
|
|
76877645ce
|
||
|
|
7d143fabb8
|
@@ -28,7 +28,7 @@ steps:
|
|||||||
- bundle install --jobs=3 --retry=3
|
- bundle install --jobs=3 --retry=3
|
||||||
- yarn install
|
- yarn install
|
||||||
- rake css:build
|
- rake css:build
|
||||||
- bundle exec rspec
|
- rake spec
|
||||||
- name: rebuild-cache
|
- name: rebuild-cache
|
||||||
image: drillster/drone-volume-cache
|
image: drillster/drone-volume-cache
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
28
.env.example
28
.env.example
@@ -1,37 +1,11 @@
|
|||||||
SMTP_SERVER=smtp.example.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_LOGIN=accounts
|
|
||||||
SMTP_PASSWORD=123abc
|
|
||||||
SMTP_FROM_ADDRESS=accounts@example.com
|
|
||||||
SMTP_DOMAIN=example.com
|
|
||||||
SMTP_AUTH_METHOD=plain
|
|
||||||
SMTP_ENABLE_STARTTLS=auto
|
|
||||||
|
|
||||||
REDIS_URL='redis://localhost:6379/1'
|
|
||||||
|
|
||||||
LDAP_HOST=localhost
|
LDAP_HOST=localhost
|
||||||
LDAP_PORT=389
|
LDAP_PORT=389
|
||||||
LDAP_ADMIN_PASSWORD=passthebutter
|
LDAP_ADMIN_PASSWORD=passthebutter
|
||||||
LDAP_SUFFIX='dc=kosmos,dc=org'
|
LDAP_SUFFIX="dc=kosmos,dc=org"
|
||||||
|
|
||||||
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
|
||||||
|
|
||||||
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
|
|
||||||
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
|
|
||||||
MASTODON_PUBLIC_URL='https://kosmos.social'
|
|
||||||
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
|
||||||
|
|
||||||
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
|
|
||||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||||
|
|
||||||
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||||
|
|
||||||
LNDHUB_API_URL='http://localhost:3023'
|
LNDHUB_API_URL='http://localhost:3023'
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
|
||||||
LNDHUB_ADMIN_UI=true
|
|
||||||
LNDHUB_PG_HOST=localhost
|
|
||||||
LNDHUB_PG_PORT=5432
|
|
||||||
LNDHUB_PG_DATABASE=lndhub
|
|
||||||
LNDHUB_PG_USERNAME=lndhub
|
|
||||||
LNDHUB_PG_PASSWORD=''
|
|
||||||
|
|||||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
|
||||||
|
BTCPAY_API_URL='http://10.1.1.163:23001/api/v1'
|
||||||
|
LNDHUB_API_URL='http://10.1.1.163:3023'
|
||||||
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||||
|
|
||||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||||
|
LNDHUB_API_URL='http://localhost:3023'
|
||||||
LNDHUB_API_URL='http://localhost:3026'
|
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
|
||||||
|
|
||||||
WEBHOOKS_ALLOWED_IPS='10.1.1.23'
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
name-template: 'v$RESOLVED_VERSION'
|
|
||||||
tag-template: 'v$RESOLVED_VERSION'
|
|
||||||
version-resolver:
|
|
||||||
major:
|
|
||||||
labels:
|
|
||||||
- 'release/major'
|
|
||||||
minor:
|
|
||||||
labels:
|
|
||||||
- 'release/minor'
|
|
||||||
patch:
|
|
||||||
labels:
|
|
||||||
- 'release/patch'
|
|
||||||
default: patch
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
name: Release Drafter
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
jobs:
|
|
||||||
release_drafter_job:
|
|
||||||
name: Update release notes draft
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Release Drafter
|
|
||||||
uses: https://github.com/raucao/gitea-release-drafter@dev
|
|
||||||
15
Dockerfile
15
Dockerfile
@@ -1,13 +1,8 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM ruby:2.7.6
|
FROM ruby:2.7.6
|
||||||
|
RUN apt-get update -qq && apt-get install -y curl ldap-utils
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
|
|
||||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
|
||||||
ldap-utils tini
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
RUN apt-get update && apt-get install -y nodejs
|
RUN apt-get update && apt-get install -y nodejs
|
||||||
|
|
||||||
WORKDIR /akkounts
|
WORKDIR /akkounts
|
||||||
COPY Gemfile /akkounts/Gemfile
|
COPY Gemfile /akkounts/Gemfile
|
||||||
COPY Gemfile.lock /akkounts/Gemfile.lock
|
COPY Gemfile.lock /akkounts/Gemfile.lock
|
||||||
@@ -17,5 +12,11 @@ RUN gem install foreman
|
|||||||
RUN npm install -g yarn
|
RUN npm install -g yarn
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
# Add a script to be executed every time the container starts.
|
||||||
|
COPY docker/entrypoint.sh /usr/bin/
|
||||||
|
RUN chmod +x /usr/bin/entrypoint.sh
|
||||||
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Configure the main process to run when running the image
|
||||||
|
CMD ["bin", "dev"]
|
||||||
|
|||||||
8
Gemfile
8
Gemfile
@@ -32,14 +32,12 @@ gem 'lockbox'
|
|||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
gem 'warden'
|
gem 'warden'
|
||||||
gem 'devise', '~> 4.9.0'
|
gem 'devise'
|
||||||
gem 'devise_ldap_authenticatable'
|
gem 'devise_ldap_authenticatable'
|
||||||
gem 'net-ldap'
|
gem 'net-ldap'
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
gem "rqrcode", "~> 2.0"
|
gem "rqrcode", "~> 2.0"
|
||||||
gem 'rails-settings-cached', '~> 2.8.3'
|
|
||||||
gem 'pagy', '~> 6.0', '>= 6.0.2'
|
|
||||||
|
|
||||||
# HTTP requests
|
# HTTP requests
|
||||||
gem 'faraday'
|
gem 'faraday'
|
||||||
@@ -48,10 +46,6 @@ gem 'faraday'
|
|||||||
gem 'sidekiq', '< 7'
|
gem 'sidekiq', '< 7'
|
||||||
gem 'sidekiq-scheduler'
|
gem 'sidekiq-scheduler'
|
||||||
|
|
||||||
# Monitoring
|
|
||||||
gem "sentry-ruby"
|
|
||||||
gem "sentry-rails"
|
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# Use sqlite3 as the database for Active Record
|
# Use sqlite3 as the database for Active Record
|
||||||
gem 'sqlite3', '~> 1.4'
|
gem 'sqlite3', '~> 1.4'
|
||||||
|
|||||||
23
Gemfile.lock
23
Gemfile.lock
@@ -95,7 +95,7 @@ GEM
|
|||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
devise (4.9.0)
|
devise (4.8.1)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
@@ -178,7 +178,6 @@ GEM
|
|||||||
nokogiri (1.13.9-x86_64-linux)
|
nokogiri (1.13.9-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
pagy (6.0.2)
|
|
||||||
pg (1.2.3)
|
pg (1.2.3)
|
||||||
public_suffix (5.0.0)
|
public_suffix (5.0.0)
|
||||||
puma (4.3.12)
|
puma (4.3.12)
|
||||||
@@ -207,9 +206,6 @@ GEM
|
|||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.4.3)
|
rails-html-sanitizer (1.4.3)
|
||||||
loofah (~> 2.3)
|
loofah (~> 2.3)
|
||||||
rails-settings-cached (2.8.3)
|
|
||||||
activerecord (>= 5.0.0)
|
|
||||||
railties (>= 5.0.0)
|
|
||||||
railties (7.0.4)
|
railties (7.0.4)
|
||||||
actionpack (= 7.0.4)
|
actionpack (= 7.0.4)
|
||||||
activesupport (= 7.0.4)
|
activesupport (= 7.0.4)
|
||||||
@@ -226,9 +222,9 @@ GEM
|
|||||||
redis-client (0.11.2)
|
redis-client (0.11.2)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.6.1)
|
regexp_parser (2.6.1)
|
||||||
responders (3.1.0)
|
responders (3.0.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.2)
|
railties (>= 5.0)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rqrcode (2.1.2)
|
rqrcode (2.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
@@ -254,11 +250,6 @@ GEM
|
|||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rufus-scheduler (3.8.2)
|
rufus-scheduler (3.8.2)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
sentry-rails (5.8.0)
|
|
||||||
railties (>= 5.0)
|
|
||||||
sentry-ruby (~> 5.8.0)
|
|
||||||
sentry-ruby (5.8.0)
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
sidekiq (6.5.5)
|
sidekiq (6.5.5)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
@@ -320,7 +311,7 @@ DEPENDENCIES
|
|||||||
capybara
|
capybara
|
||||||
cssbundling-rails
|
cssbundling-rails
|
||||||
database_cleaner
|
database_cleaner
|
||||||
devise (~> 4.9.0)
|
devise
|
||||||
devise_ldap_authenticatable
|
devise_ldap_authenticatable
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
@@ -333,15 +324,11 @@ DEPENDENCIES
|
|||||||
listen (~> 3.2)
|
listen (~> 3.2)
|
||||||
lockbox
|
lockbox
|
||||||
net-ldap
|
net-ldap
|
||||||
pagy (~> 6.0, >= 6.0.2)
|
|
||||||
pg (~> 1.2.3)
|
pg (~> 1.2.3)
|
||||||
puma (~> 4.1)
|
puma (~> 4.1)
|
||||||
rails (~> 7.0.2)
|
rails (~> 7.0.2)
|
||||||
rails-settings-cached (~> 2.8.3)
|
|
||||||
rqrcode (~> 2.0)
|
rqrcode (~> 2.0)
|
||||||
rspec-rails
|
rspec-rails
|
||||||
sentry-rails
|
|
||||||
sentry-ruby
|
|
||||||
sidekiq (< 7)
|
sidekiq (< 7)
|
||||||
sidekiq-scheduler
|
sidekiq-scheduler
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ so:
|
|||||||
|
|
||||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||||
Docker Desktop)
|
Docker Desktop)
|
||||||
2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml`
|
2. Uncomment the `web` section in `docker-compose.yml`
|
||||||
3. Run `docker compose up` and wait until 389ds announces its successful start
|
3. Run `docker compose up` and wait until 389ds announces its successful start
|
||||||
in the log output
|
in the log output
|
||||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||||
5. `docker compose run web rails ldap:setup`
|
5. `docker compose run web rails ldap:setup`
|
||||||
6. `docker compose run web rails db:setup`
|
5. `docker compose run web rails db:setup`
|
||||||
|
|
||||||
After these steps, you should have a working Rails app with a handful of test
|
After these steps, you should have a working Rails app with a handful of test
|
||||||
users running on [http://localhost:3000](http://localhost:3000).
|
users running on [http://localhost:3000](http://localhost:3000).
|
||||||
@@ -81,15 +81,12 @@ with a fresh installation, delete both that directory as well as the container.
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### Rails
|
|
||||||
|
|
||||||
* [Ruby on Rails](https://guides.rubyonrails.org/)
|
* [Ruby on Rails](https://guides.rubyonrails.org/)
|
||||||
* [Pagination](https://ddnexus.github.io/pagy/)
|
* [Sass](https://sass-lang.com/documentation)
|
||||||
|
|
||||||
### Front-end
|
### Front-end
|
||||||
|
|
||||||
* [Tailwind CSS](https://tailwindcss.com/)
|
* [Tailwind CSS](https://tailwindcss.com/)
|
||||||
* [Sass](https://sass-lang.com/documentation)
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
|
|
||||||
@import "components/base";
|
@import "components/base";
|
||||||
@import "components/buttons";
|
@import "components/buttons";
|
||||||
@import "components/dashboard_services";
|
|
||||||
@import "components/forms";
|
@import "components/forms";
|
||||||
@import "components/links";
|
@import "components/links";
|
||||||
@import "components/notifications";
|
@import "components/notifications";
|
||||||
@import "components/pagination";
|
|
||||||
@import "components/tables";
|
@import "components/tables";
|
||||||
|
|||||||
@@ -36,18 +36,10 @@
|
|||||||
@apply mb-4 leading-6;
|
@apply mb-4 leading-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
main p:last-child {
|
|
||||||
@apply mb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main ul {
|
main ul {
|
||||||
@apply mb-6;
|
@apply mb-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
main ul:last-child {
|
|
||||||
@apply mb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main ul li {
|
main ul li {
|
||||||
@apply leading-6;
|
@apply leading-6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@layer components {
|
@layer components {
|
||||||
.btn {
|
.btn {
|
||||||
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
|
@apply font-semibold rounded-md leading-none cursor-pointer text-center
|
||||||
transition-colors duration-75 focus:outline-none focus:ring-4;
|
transition-colors duration-75 focus:outline-none focus:ring-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.services > div > a {
|
|
||||||
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@layer components {
|
@layer components {
|
||||||
input[type=text], input[type=email], input[type=password],
|
input[type=text], input[type=email], input[type=password],
|
||||||
input[type=number], select, textarea {
|
input[type=number], select {
|
||||||
@apply rounded-md bg-gray-100 focus:bg-white
|
@apply mt-1 rounded-md bg-gray-100 focus:bg-white
|
||||||
border-transparent focus:border-transparent focus:ring-2
|
border-transparent focus:border-transparent focus:ring-2
|
||||||
focus:ring-blue-600 focus:ring-opacity-75;
|
focus:ring-blue-600 focus:ring-opacity-75;
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,6 @@
|
|||||||
@apply inline-block;
|
@apply inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field_with_errors input {
|
|
||||||
@apply w-full bg-red-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-msg {
|
.error-msg {
|
||||||
@apply text-red-700;
|
@apply text-red-700;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,10 @@
|
|||||||
&:visited { @apply text-indigo-600; }
|
&:visited { @apply text-indigo-600; }
|
||||||
&:active { @apply text-red-600; }
|
&:active { @apply text-red-600; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.devise-links {
|
||||||
|
a {
|
||||||
|
@apply ks-text-link;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.pagy-nav.pagination {
|
|
||||||
@apply isolate inline-flex -space-x-px rounded-md shadow-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page:not(.prev):not(.next) {
|
|
||||||
@apply hidden sm:inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page.next a {
|
|
||||||
@apply relative inline-flex items-center rounded-r-md border
|
|
||||||
border-gray-300 bg-white px-3 py-2 text-sm font-medium
|
|
||||||
text-gray-500 hover:bg-gray-100 focus:z-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page.prev a {
|
|
||||||
@apply relative inline-flex items-center rounded-l-md border
|
|
||||||
border-gray-300 bg-white px-3 py-2 text-sm font-medium
|
|
||||||
text-gray-500 hover:bg-gray-100 focus:z-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page.next.disabled {
|
|
||||||
@apply relative inline-flex items-center rounded-r-md border
|
|
||||||
border-gray-300 bg-gray-100 px-3 py-2 text-sm font-medium
|
|
||||||
text-gray-400 focus:z-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page.prev.disabled {
|
|
||||||
@apply relative inline-flex items-center rounded-l-md border
|
|
||||||
border-gray-300 bg-gray-100 px-3 py-2 text-sm font-medium
|
|
||||||
text-gray-400 focus:z-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page a, .page.gap {
|
|
||||||
@apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative
|
|
||||||
inline-flex items-center border px-4 py-2 text-sm font-medium
|
|
||||||
focus:z-20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagy-nav .page.active {
|
|
||||||
@apply z-10 border-indigo-500 bg-indigo-50 text-indigo-600 relative
|
|
||||||
inline-flex items-center border px-4 py-2 text-sm font-medium
|
|
||||||
focus:z-20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,30 +7,16 @@
|
|||||||
@apply text-left;
|
@apply text-left;
|
||||||
}
|
}
|
||||||
|
|
||||||
table thead th {
|
table th {
|
||||||
@apply pb-3.5 text-sm font-normal uppercase text-gray-500;
|
@apply pb-3.5 text-sm font-normal uppercase text-gray-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tbody th {
|
|
||||||
@apply text-left font-normal text-gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
table th:not(:last-of-type),
|
table th:not(:last-of-type),
|
||||||
table td:not(:last-of-type) {
|
table td:not(:last-of-type) {
|
||||||
@apply pr-2;
|
@apply pr-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td, tbody th {
|
table td {
|
||||||
@apply py-2;
|
@apply py-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.divided {
|
|
||||||
@apply divide-y divide-gray-300;
|
|
||||||
}
|
|
||||||
table.divided tbody {
|
|
||||||
@apply divide-y divide-gray-200;
|
|
||||||
}
|
|
||||||
table.divided td, table.divided tbody th {
|
|
||||||
@apply py-3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
|
|
||||||
<label class="block">
|
|
||||||
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
|
|
||||||
<%= @title %>
|
|
||||||
</p>
|
|
||||||
<% if @descripton.present? %>
|
|
||||||
<p class="text-gray-500">
|
|
||||||
<%= @descripton %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
<%= content %>
|
|
||||||
</label>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module FormElements
|
|
||||||
class FieldsetComponent < ViewComponent::Base
|
|
||||||
def initialize(tag: "li", title:, description: nil)
|
|
||||||
@tag = tag
|
|
||||||
@title = title
|
|
||||||
@descripton = description
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<%= tag.public_send @tag, class: "flex items-center justify-between mb-6 last:mb-0",
|
|
||||||
data: @form.present? ? {
|
|
||||||
controller: "settings--toggle",
|
|
||||||
:'settings--toggle-switch-enabled-value' => @enabled.to_s
|
|
||||||
} : nil do %>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<label class="font-bold mb-1"><%= @title %></label>
|
|
||||||
<p class="text-gray-500"><%= @descripton %></p>
|
|
||||||
</div>
|
|
||||||
<div class="relative ml-4 inline-flex flex-shrink-0">
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @enabled,
|
|
||||||
input_enabled: @input_enabled,
|
|
||||||
class_names: @form.present? ? "hidden" : nil,
|
|
||||||
data: {
|
|
||||||
:'settings--toggle-target' => "button",
|
|
||||||
action: "settings--toggle#toggleSwitch"
|
|
||||||
}) %>
|
|
||||||
<% if @form.present? %>
|
|
||||||
<%= @form.check_box @attribute, {
|
|
||||||
checked: @enabled,
|
|
||||||
data: { :'settings--toggle-target' => "checkbox" }
|
|
||||||
}, "true", "false" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module FormElements
|
|
||||||
class FieldsetToggleComponent < ViewComponent::Base
|
|
||||||
def initialize(form: nil, attribute: nil, tag: "li", enabled: false,
|
|
||||||
input_enabled: true, title:, description:)
|
|
||||||
@form = form
|
|
||||||
@attribute = attribute
|
|
||||||
@tag = tag
|
|
||||||
@enabled = enabled
|
|
||||||
@input_enabled = input_enabled
|
|
||||||
@title = title
|
|
||||||
@descripton = description
|
|
||||||
@button_text = @enabled ? "Switch off" : "Switch on"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<%= button_tag type: "button", name: "toggle", data: @data,
|
|
||||||
role: "switch", aria: { checked: @enabled.to_s },
|
|
||||||
tabindex: @tabindex, disabled: !@input_enabled,
|
|
||||||
class: "#{ @enabled ? 'bg-blue-600' : 'bg-gray-200' }
|
|
||||||
#{ @class_names.present? ? @class_names : '' }
|
|
||||||
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer
|
|
||||||
rounded-full border-2 border-transparent transition-colors
|
|
||||||
duration-200 ease-in-out focus:outline-none focus:ring-2
|
|
||||||
focus:ring-blue-600 focus:ring-offset-2" do %>
|
|
||||||
<span class="sr-only"><%= @button_text %></span>
|
|
||||||
<span aria-hidden="true" data-settings--toggle-target="switch"
|
|
||||||
class="<%= @enabled ? 'translate-x-5' : 'translate-x-0' %>
|
|
||||||
pointer-events-none inline-block h-5 w-5 transform rounded-full
|
|
||||||
bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module FormElements
|
|
||||||
class ToggleComponent < ViewComponent::Base
|
|
||||||
def initialize(enabled:, input_enabled: true, data: nil, class_names: nil, tabindex: nil)
|
|
||||||
@enabled = !!enabled
|
|
||||||
@input_enabled = input_enabled
|
|
||||||
@data = data
|
|
||||||
@class_names = class_names
|
|
||||||
@tabindex = tabindex
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<dl class="grid grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-12">
|
|
||||||
<%= content %>
|
|
||||||
</dl>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class QuickstatsContainerComponent < ViewComponent::Base
|
|
||||||
end
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="">
|
|
||||||
<dt class="mb-2 text-gray-500">
|
|
||||||
<%= @title %>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
<% if @type == :number %>
|
|
||||||
<span class="text-2xl"><%= number_with_delimiter @value %></span>
|
|
||||||
<% else %>
|
|
||||||
<span class="text-2xl"><%= @value %></span>
|
|
||||||
<% end %>
|
|
||||||
<% if @unit %>
|
|
||||||
<span><%= @unit %></span>
|
|
||||||
<% end %>
|
|
||||||
<% if @meta %>
|
|
||||||
<span class="text-gray-500"><%= @meta %></span>
|
|
||||||
<% end %>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class QuickstatsItemComponent < ViewComponent::Base
|
|
||||||
def initialize(type:, title:, value:, unit: nil, meta: nil, icon_name: nil, icon_color_class: nil)
|
|
||||||
@type = type
|
|
||||||
@title = title
|
|
||||||
@value = value
|
|
||||||
@unit = unit
|
|
||||||
@meta = meta
|
|
||||||
@icon_name = icon_name
|
|
||||||
@icon_color_class = icon_color_class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SidenavLinkComponent < ViewComponent::Base
|
class SidenavLinkComponent < ViewComponent::Base
|
||||||
def initialize(name:, level: 1, path:, icon:, active: false, disabled: false)
|
def initialize(name:, path:, icon:, active: false, disabled: false)
|
||||||
@name = name
|
@name = name
|
||||||
@level = level
|
|
||||||
@path = path
|
@path = path
|
||||||
@icon = icon
|
@icon = icon
|
||||||
@active = active
|
@active = active
|
||||||
@@ -13,15 +12,12 @@ class SidenavLinkComponent < ViewComponent::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def class_names_link(path)
|
def class_names_link(path)
|
||||||
px = @level == 1 ? "px-4" : "pl-8 pr-4"
|
|
||||||
base = "#{px} py-2 group border-l-4 flex items-center text-base font-medium"
|
|
||||||
|
|
||||||
if @active
|
if @active
|
||||||
"#{base} bg-teal-50 border-teal-500 text-teal-700 hover:bg-teal-50 hover:text-teal-700"
|
"bg-teal-50 border-teal-500 text-teal-700 hover:bg-teal-50 hover:text-teal-700 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
|
||||||
elsif @disabled
|
elsif @disabled
|
||||||
"#{base} border-transparent text-gray-400 hover:bg-gray-50"
|
"border-transparent text-gray-400 hover:bg-gray-50 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
|
||||||
else
|
else
|
||||||
"#{base} border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900"
|
"border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-4 py-2 flex items-center text-base font-medium"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,10 @@
|
|||||||
<div class="md:col-span-4 mt-4 md:mt-0">
|
<div class="md:col-span-4 mt-4 md:mt-0">
|
||||||
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
|
||||||
<% if @balance %>
|
<% if @balance %>
|
||||||
<span class="text-2xl"><%= number_with_delimiter @balance %></span>
|
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
|
||||||
<span class="text-xl">sats</span>
|
|
||||||
<br>
|
|
||||||
<span class="text-sm text-gray-500">Available balance</span>
|
<span class="text-sm text-gray-500">Available balance</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-2xl">n/a</span>
|
<span class="text-xl">n/a sats</span><br>
|
||||||
<span class="text-xl">sats</span>
|
|
||||||
<br>
|
|
||||||
<span class="text-sm text-gray-500">Balance unavailable</span>
|
<span class="text-sm text-gray-500">Balance unavailable</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class Admin::BaseController < ApplicationController
|
class Admin::BaseController < ApplicationController
|
||||||
include Pagy::Backend
|
|
||||||
|
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :authorize_admin
|
before_action :authorize_admin
|
||||||
@@ -8,4 +7,5 @@ class Admin::BaseController < ApplicationController
|
|||||||
def set_context
|
def set_context
|
||||||
@context = :admin
|
@context = :admin
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,12 +5,7 @@ class Admin::DonationsController < Admin::BaseController
|
|||||||
# GET /donations
|
# GET /donations
|
||||||
# GET /donations.json
|
# GET /donations.json
|
||||||
def index
|
def index
|
||||||
@pagy, @donations = pagy(Donation.all.order('created_at desc'))
|
@donations = Donation.all
|
||||||
|
|
||||||
@stats = {
|
|
||||||
overall_sats: @donations.all.sum("amount_sats"),
|
|
||||||
donor_count: Donation.distinct.count(:user_id)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET /donations/1
|
# GET /donations/1
|
||||||
@@ -34,14 +29,10 @@ class Admin::DonationsController < Admin::BaseController
|
|||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @donation.save
|
if @donation.save
|
||||||
format.html do
|
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
|
||||||
redirect_to admin_donation_url(@donation), flash: {
|
|
||||||
success: 'Donation was successfully created.'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
format.json { render :show, status: :created, location: @donation }
|
format.json { render :show, status: :created, location: @donation }
|
||||||
else
|
else
|
||||||
format.html { render :new, status: :unprocessable_entity }
|
format.html { render :new }
|
||||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -52,14 +43,10 @@ class Admin::DonationsController < Admin::BaseController
|
|||||||
def update
|
def update
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @donation.update(donation_params)
|
if @donation.update(donation_params)
|
||||||
format.html do
|
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
|
||||||
redirect_to admin_donation_url(@donation), flash: {
|
|
||||||
success: 'Donation was successfully updated.'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
format.json { render :show, status: :ok, location: @donation }
|
format.json { render :show, status: :ok, location: @donation }
|
||||||
else
|
else
|
||||||
format.html { render :edit, status: :unprocessable_entity }
|
format.html { render :edit }
|
||||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -70,10 +57,7 @@ class Admin::DonationsController < Admin::BaseController
|
|||||||
def destroy
|
def destroy
|
||||||
@donation.destroy
|
@donation.destroy
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do redirect_to admin_donations_url, flash: {
|
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
|
||||||
success: 'Donation was successfully destroyed.'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
format.json { head :no_content }
|
format.json { head :no_content }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
class Admin::InvitationsController < Admin::BaseController
|
class Admin::InvitationsController < Admin::BaseController
|
||||||
def index
|
def index
|
||||||
@current_section = :invitations
|
@current_section = :invitations
|
||||||
@pagy, @invitations_used = pagy(Invitation.used.order('used_at desc'))
|
@invitations_unused_count = Invitation.unused.count
|
||||||
|
@users_with_referrals_count = Invitation.used.distinct.count(:user_id)
|
||||||
@stats = {
|
@invitations_used = Invitation.used.order('used_at desc')
|
||||||
available: Invitation.unused.count,
|
|
||||||
accepted: @invitations_used.length,
|
|
||||||
users_with_referrals: Invitation.used.distinct.count(:user_id)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
45
app/controllers/admin/ldap_users_controller.rb
Normal file
45
app/controllers/admin/ldap_users_controller.rb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
class Admin::LdapUsersController < Admin::BaseController
|
||||||
|
before_action :set_current_section
|
||||||
|
|
||||||
|
def index
|
||||||
|
attributes = %w{dn cn uid mail admin}
|
||||||
|
filter = Net::LDAP::Filter.eq("uid", "*")
|
||||||
|
|
||||||
|
@ou = params[:ou] || "kosmos.org"
|
||||||
|
treebase = "ou=#{@ou},cn=users,dc=kosmos,dc=org"
|
||||||
|
|
||||||
|
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
|
entries.sort_by! { |e| e.cn[0] }
|
||||||
|
|
||||||
|
@entries = entries.collect do |e|
|
||||||
|
{
|
||||||
|
uid: e.uid.first,
|
||||||
|
mail: e.try(:mail) ? e.mail.first : nil,
|
||||||
|
admin: e.try(:admin) ? 'admin' : nil
|
||||||
|
# password: e.userpassword.first
|
||||||
|
}
|
||||||
|
end
|
||||||
|
# ldap_client.get_operation_result
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ldap_client
|
||||||
|
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
|
||||||
|
port: ldap_config['port'],
|
||||||
|
# encryption: ldap_config['ssl'],
|
||||||
|
auth: {
|
||||||
|
method: :simple,
|
||||||
|
username: ldap_config['admin_user'],
|
||||||
|
password: ldap_config['admin_password']
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def ldap_config
|
||||||
|
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_current_section
|
||||||
|
@current_section = :ldap_users
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
class Admin::LightningController < Admin::BaseController
|
|
||||||
before_action :check_feature_enabled
|
|
||||||
|
|
||||||
def index
|
|
||||||
@current_section = :lightning
|
|
||||||
|
|
||||||
@users = User.pluck(:cn, :ou, :ln_account)
|
|
||||||
@accounts = LndhubAccount.with_balances.order(balance: :desc).to_a
|
|
||||||
|
|
||||||
@ln = {}
|
|
||||||
@ln[:current_balance] = LndhubAccount.current.joins(:ledgers).sum("account_ledgers.amount")
|
|
||||||
@ln[:users_with_sats] = @accounts.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_feature_enabled
|
|
||||||
if !Setting.lndhub_admin_enabled?
|
|
||||||
flash[:alert] = "Lightning Admin UI not enabled"
|
|
||||||
redirect_to admin_root_path and return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
class Admin::Settings::RegistrationsController < Admin::SettingsController
|
|
||||||
def index
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
update_settings
|
|
||||||
|
|
||||||
redirect_to admin_settings_registrations_path, flash: {
|
|
||||||
success: "Settings saved"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
class Admin::Settings::ServicesController < Admin::SettingsController
|
|
||||||
def index
|
|
||||||
@service = params[:s]
|
|
||||||
|
|
||||||
if @service.blank?
|
|
||||||
redirect_to admin_settings_services_path(params: { s: "discourse" })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
service = params.require(:service)
|
|
||||||
|
|
||||||
update_settings
|
|
||||||
|
|
||||||
redirect_to admin_settings_services_path(params: { s: service }), flash: {
|
|
||||||
success: "Settings saved"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
class Admin::SettingsController < Admin::BaseController
|
|
||||||
before_action :set_current_section
|
|
||||||
|
|
||||||
def index
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_settings
|
|
||||||
@errors = ActiveModel::Errors.new(Setting.new)
|
|
||||||
changed_keys = []
|
|
||||||
|
|
||||||
setting_params.keys.each do |key|
|
|
||||||
next if setting_params[key].nil? ||
|
|
||||||
(Setting.send(key).to_s == setting_params[key].strip)
|
|
||||||
changed_keys.push(key)
|
|
||||||
setting = Setting.new(var: key)
|
|
||||||
setting.value = setting_params[key].strip
|
|
||||||
unless setting.valid?
|
|
||||||
@errors.merge!(setting.errors)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if @errors.any?
|
|
||||||
render :index and return
|
|
||||||
end
|
|
||||||
|
|
||||||
changed_keys.each do |key|
|
|
||||||
Setting.send("#{key}=", setting_params[key].strip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_current_section
|
|
||||||
@current_section = :settings
|
|
||||||
end
|
|
||||||
|
|
||||||
def setting_params
|
|
||||||
params.require(:setting).permit(Setting.editable_keys.map(&:to_sym))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
class Admin::UsersController < Admin::BaseController
|
|
||||||
before_action :set_user, only: [:show]
|
|
||||||
before_action :set_current_section
|
|
||||||
|
|
||||||
def index
|
|
||||||
ldap = LdapService.new
|
|
||||||
@ou = params[:ou] || "kosmos.org"
|
|
||||||
@orgs = ldap.fetch_organizations
|
|
||||||
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
|
|
||||||
|
|
||||||
@stats = {
|
|
||||||
users_confirmed: User.where(ou: @ou).confirmed.count,
|
|
||||||
users_pending: User.where(ou: @ou).pending.count
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
if Setting.lndhub_admin_enabled?
|
|
||||||
@lndhub_user = @user.lndhub_user
|
|
||||||
end
|
|
||||||
|
|
||||||
@services_enabled = @user.services_enabled
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_user
|
|
||||||
address = params[:address].split("@")
|
|
||||||
@user = User.where(cn: address.first, ou: address.last).first
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_current_section
|
|
||||||
@current_section = :users
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -3,18 +3,6 @@ class ApplicationController < ActionController::Base
|
|||||||
render :text => exception, :status => 500
|
render :text => exception, :status => 500
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action :sentry_set_user
|
|
||||||
|
|
||||||
def sentry_set_user
|
|
||||||
return unless Setting.sentry_enabled
|
|
||||||
|
|
||||||
if user_signed_in?
|
|
||||||
Sentry.set_user(id: current_user.id, username: current_user.cn)
|
|
||||||
else
|
|
||||||
Sentry.set_user({})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def require_user_signed_in
|
def require_user_signed_in
|
||||||
unless user_signed_in?
|
unless user_signed_in?
|
||||||
redirect_to welcome_path and return
|
redirect_to welcome_path and return
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class InvitationsController < ApplicationController
|
|||||||
# GET /invitations
|
# GET /invitations
|
||||||
def index
|
def index
|
||||||
@invitations_unused = current_user.invitations.unused
|
@invitations_unused = current_user.invitations.unused
|
||||||
@invitations_used = current_user.invitations.used.order('used_at desc')
|
@invitations_used = current_user.invitations.used
|
||||||
@current_section = :invitations
|
@current_section = :invitations
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -27,10 +27,7 @@ class InvitationsController < ApplicationController
|
|||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @invitation.save
|
if @invitation.save
|
||||||
format.html do redirect_to @invitation, flash: {
|
format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' }
|
||||||
success: 'Invitation was successfully created.'
|
|
||||||
}
|
|
||||||
end
|
|
||||||
format.json { render :show, status: :created, location: @invitation }
|
format.json { render :show, status: :created, location: @invitation }
|
||||||
else
|
else
|
||||||
format.html { render :new }
|
format.html { render :new }
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class LnurlpayController < ApplicationController
|
class LnurlpayController < ApplicationController
|
||||||
before_action :check_feature_enabled
|
|
||||||
before_action :find_user_by_address
|
before_action :find_user_by_address
|
||||||
|
|
||||||
MIN_SATS = 10
|
MIN_SATS = 10
|
||||||
@@ -18,20 +17,6 @@ class LnurlpayController < ApplicationController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def keysend
|
|
||||||
http_status :not_found and return unless Setting.lndhub_keysend_enabled?
|
|
||||||
|
|
||||||
render json: {
|
|
||||||
status: "OK",
|
|
||||||
tag: "keysend",
|
|
||||||
pubkey: Setting.lndhub_public_key,
|
|
||||||
customData: [{
|
|
||||||
customKey: "696969",
|
|
||||||
customValue: @user.ln_account
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def invoice
|
def invoice
|
||||||
amount = params[:amount].to_i / 1000 # msats
|
amount = params[:amount].to_i / 1000 # msats
|
||||||
address = params[:address]
|
address = params[:address]
|
||||||
@@ -47,7 +32,7 @@ class LnurlpayController < ApplicationController
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
memo = "To #{address}"
|
memo = "Sats for #{address}"
|
||||||
memo = "#{memo}: \"#{comment}\"" if comment.present?
|
memo = "#{memo}: \"#{comment}\"" if comment.present?
|
||||||
|
|
||||||
payment_request = @user.ln_create_invoice({
|
payment_request = @user.ln_create_invoice({
|
||||||
@@ -87,9 +72,4 @@ class LnurlpayController < ApplicationController
|
|||||||
comment.length <= MAX_COMMENT_CHARS
|
comment.length <= MAX_COMMENT_CHARS
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def check_feature_enabled
|
|
||||||
http_status :not_found unless Setting.lndhub_enabled?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Users::ConfirmationsController < Devise::ConfirmationsController
|
|
||||||
# GET /resource/confirmation?confirmation_token=abcdef
|
|
||||||
def show
|
|
||||||
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
|
|
||||||
yield resource if block_given?
|
|
||||||
|
|
||||||
if resource.errors.empty?
|
|
||||||
set_flash_message!(:success, :confirmed)
|
|
||||||
resource.devise_after_confirmation
|
|
||||||
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
|
|
||||||
else
|
|
||||||
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -7,7 +7,7 @@ class WalletController < ApplicationController
|
|||||||
before_action :fetch_balance
|
before_action :fetch_balance
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||||
|
|
||||||
qrcode = RQRCode::QRCode.new(@wallet_url)
|
qrcode = RQRCode::QRCode.new(@wallet_url)
|
||||||
@svg = qrcode.as_svg(
|
@svg = qrcode.as_svg(
|
||||||
@@ -28,13 +28,13 @@ class WalletController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authenticate_with_lndhub(options={})
|
def authenticate_with_lndhub
|
||||||
if session[:ln_auth_token].present? && !options[:force_reauth]
|
if session["ln_auth_token"].present?
|
||||||
@ln_auth_token = session[:ln_auth_token]
|
@ln_auth_token = session["ln_auth_token"]
|
||||||
else
|
else
|
||||||
lndhub = Lndhub.new
|
lndhub = Lndhub.new
|
||||||
auth_token = lndhub.authenticate(current_user)
|
auth_token = lndhub.authenticate(current_user)
|
||||||
session[:ln_auth_token] = auth_token
|
session["ln_auth_token"] = auth_token
|
||||||
@ln_auth_token = auth_token
|
@ln_auth_token = auth_token
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
@@ -49,23 +49,14 @@ class WalletController < ApplicationController
|
|||||||
lndhub = Lndhub.new
|
lndhub = Lndhub.new
|
||||||
data = lndhub.balance @ln_auth_token
|
data = lndhub.balance @ln_auth_token
|
||||||
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||||
rescue
|
|
||||||
authenticate_with_lndhub(force_reauth: true)
|
|
||||||
return nil if @fetch_balance_retried
|
|
||||||
@fetch_balance_retried = true
|
|
||||||
fetch_balance
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_transactions
|
def fetch_transactions
|
||||||
lndhub = Lndhub.new
|
lndhub = Lndhub.new
|
||||||
txs = lndhub.gettxs @ln_auth_token
|
txs = lndhub.gettxs @ln_auth_token
|
||||||
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||||
|
|
||||||
process_transactions(txs + invoices)
|
process_transactions(txs + invoices)
|
||||||
rescue
|
|
||||||
authenticate_with_lndhub(force_reauth: true)
|
|
||||||
return [] if @fetch_transactions_retried
|
|
||||||
@fetch_transactions_retried = true
|
|
||||||
fetch_transactions
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_transactions(txs)
|
def process_transactions(txs)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
class WebhooksController < ApplicationController
|
|
||||||
skip_forgery_protection
|
|
||||||
|
|
||||||
before_action :authorize_request
|
|
||||||
|
|
||||||
def lndhub
|
|
||||||
begin
|
|
||||||
payload = JSON.parse(request.body.read, symbolize_names: true)
|
|
||||||
head :no_content and return unless payload[:type] == "incoming"
|
|
||||||
rescue
|
|
||||||
head :unprocessable_entity and return
|
|
||||||
end
|
|
||||||
|
|
||||||
user = User.find_by!(ln_account: payload[:user_login])
|
|
||||||
|
|
||||||
# TODO make configurable
|
|
||||||
notify_xmpp(user.address, payload[:amount], payload[:memo])
|
|
||||||
|
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def notify_xmpp(address, amt_sats, memo)
|
|
||||||
payload = {
|
|
||||||
type: "normal",
|
|
||||||
from: "kosmos.org", # TODO domain config
|
|
||||||
to: address,
|
|
||||||
subject: "Sats received!",
|
|
||||||
body: "#{amt_sats} sats received in your Lightning wallet:\n> #{memo}"
|
|
||||||
}
|
|
||||||
XmppSendMessageJob.perform_later(payload)
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_request
|
|
||||||
if !ENV['WEBHOOKS_ALLOWED_IPS'].split(',').include?(request.remote_ip)
|
|
||||||
head :forbidden and return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
include Pagy::Frontend
|
|
||||||
|
|
||||||
def sats_to_btc(sats)
|
def sats_to_btc(sats)
|
||||||
sats.to_f / 100000000
|
sats.to_f / 100000000
|
||||||
end
|
end
|
||||||
@@ -12,10 +10,5 @@ module ApplicationHelper
|
|||||||
"text-gray-300 hover:bg-gray-900/30 hover:text-white active:bg-gray-900/30 active:text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block"
|
"text-gray-300 hover:bg-gray-900/30 hover:text-white active:bg-gray-900/30 active:text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Colors available: gray, red, yellow, green, blue, purple, pink
|
|
||||||
# (Add more colors by adding classes to the safelist in tailwind.config.js)
|
|
||||||
def badge(text, color)
|
|
||||||
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
2
app/helpers/ldap_users_helper.rb
Normal file
2
app/helpers/ldap_users_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
module LdapUsersHelper
|
||||||
|
end
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module UsersHelper
|
|
||||||
end
|
|
||||||
@@ -4,10 +4,6 @@ export default class extends Controller {
|
|||||||
static targets = ["buttons", "countdown"]
|
static targets = ["buttons", "countdown"]
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
// Devise timeoutable ends up adding a second flash message without content
|
|
||||||
// TODO investigate bug
|
|
||||||
if (this.element.textContent.trim() == "true") return;
|
|
||||||
|
|
||||||
const timeoutSeconds = parseInt(this.data.get("timeout"));
|
const timeoutSeconds = parseInt(this.data.get("timeout"));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Controller } from "@hotwired/stimulus"
|
|
||||||
|
|
||||||
export default class extends Controller {
|
|
||||||
static targets = [ "button", "switch", "checkbox" ]
|
|
||||||
static values = { switchEnabled: Boolean }
|
|
||||||
|
|
||||||
connect () {
|
|
||||||
this.buttonTarget.classList.remove("hidden")
|
|
||||||
this.checkboxTarget.classList.add("hidden")
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSwitch () {
|
|
||||||
this.switchEnabledValue = !this.switchEnabledValue
|
|
||||||
this.checkboxTarget.checked = this.switchEnabledValue
|
|
||||||
|
|
||||||
if (this.switchEnabledValue) {
|
|
||||||
this.buttonTarget.setAttribute("aria-checked", "true");
|
|
||||||
this.buttonTarget.classList.remove("bg-gray-200")
|
|
||||||
this.buttonTarget.classList.add("bg-blue-600")
|
|
||||||
this.switchTarget.classList.remove("translate-x-0")
|
|
||||||
this.switchTarget.classList.add("translate-x-5")
|
|
||||||
} else {
|
|
||||||
this.buttonTarget.setAttribute("aria-checked", "false");
|
|
||||||
this.buttonTarget.classList.remove("bg-blue-600")
|
|
||||||
this.buttonTarget.classList.add("bg-gray-200")
|
|
||||||
this.switchTarget.classList.remove("translate-x-5")
|
|
||||||
this.switchTarget.classList.add("translate-x-0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
class CreateLndhubAccountJob < ApplicationJob
|
|
||||||
queue_as :default
|
|
||||||
|
|
||||||
def perform(user)
|
|
||||||
return if user.ln_account.present? && user.ln_password.present?
|
|
||||||
|
|
||||||
lndhub = LndhubV2.new
|
|
||||||
credentials = lndhub.create_account
|
|
||||||
|
|
||||||
user.update! ln_account: credentials["login"],
|
|
||||||
ln_password: credentials["password"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
13
app/jobs/create_lndhub_wallet_job.rb
Normal file
13
app/jobs/create_lndhub_wallet_job.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class CreateLndhubWalletJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
def perform(user)
|
||||||
|
return if user.ln_login.present? && user.ln_password.present?
|
||||||
|
|
||||||
|
lndhub = Lndhub.new
|
||||||
|
credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
|
||||||
|
|
||||||
|
user.update! ln_login: credentials["login"],
|
||||||
|
ln_password: credentials["password"]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class XmppExchangeContactsJob < ApplicationJob
|
class ExchangeXmppContactsJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(inviter, username, domain)
|
def perform(inviter, username, domain)
|
||||||
@@ -7,12 +7,12 @@ class XmppExchangeContactsJob < ApplicationJob
|
|||||||
ejabberd.add_rosteritem({
|
ejabberd.add_rosteritem({
|
||||||
"localuser": username, "localhost": domain,
|
"localuser": username, "localhost": domain,
|
||||||
"user": inviter.cn, "host": inviter.ou,
|
"user": inviter.cn, "host": inviter.ou,
|
||||||
"nick": inviter.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both"
|
"nick": inviter.cn, "group": "Friends", "subs": "both"
|
||||||
})
|
})
|
||||||
ejabberd.add_rosteritem({
|
ejabberd.add_rosteritem({
|
||||||
"localuser": inviter.cn, "localhost": inviter.ou,
|
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||||
"user": username, "host": domain,
|
"user": username, "host": domain,
|
||||||
"nick": username, "group": Setting.ejabberd_buddy_roster, "subs": "both"
|
"nick": username, "group": "Friends", "subs": "both"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
class XmppSendMessageJob < ApplicationJob
|
|
||||||
queue_as :default
|
|
||||||
|
|
||||||
def perform(payload)
|
|
||||||
ejabberd = EjabberdApiClient.new
|
|
||||||
ejabberd.send_message payload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
class ApplicationMailer < ActionMailer::Base
|
class ApplicationMailer < ActionMailer::Base
|
||||||
|
default from: 'from@example.com'
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# A custom mailer that can be used from the Rails console for one-off emails
|
|
||||||
# today, and later connected from an admin panel mailing page.
|
|
||||||
#
|
|
||||||
# Assign any template variables you want to use:
|
|
||||||
#
|
|
||||||
# user = User.first
|
|
||||||
#
|
|
||||||
# Create the email body from a custom email template file:
|
|
||||||
#
|
|
||||||
# body = ERB.new(File.read('./tmp/mailer-1.txt.erb')).result binding
|
|
||||||
#
|
|
||||||
# Send email via Sidekiq:
|
|
||||||
#
|
|
||||||
# CustomMailer.with(user: user, subject: "Important announcement", body: body).custom_message.deliver_later
|
|
||||||
#
|
|
||||||
class CustomMailer < ApplicationMailer
|
|
||||||
def custom_message
|
|
||||||
@user = params[:user]
|
|
||||||
@subject = params[:subject]
|
|
||||||
@body = params[:body]
|
|
||||||
mail(to: @user.email, subject: @subject)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -3,9 +3,7 @@ class Donation < ApplicationRecord
|
|||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
# Validations
|
# Validations
|
||||||
validates_presence_of :user
|
|
||||||
validates_presence_of :amount_sats
|
validates_presence_of :amount_sats
|
||||||
validates_presence_of :paid_at
|
|
||||||
|
|
||||||
# Hooks
|
# Hooks
|
||||||
# TODO before_create :store_fiat_value
|
# TODO before_create :store_fiat_value
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
class Invitation < ApplicationRecord
|
class Invitation < ApplicationRecord
|
||||||
# Relations
|
# Relations
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :invitee, class_name: "User", foreign_key: 'invited_user_id', optional: true
|
|
||||||
|
|
||||||
# Validations
|
# Validations
|
||||||
validates_presence_of :user
|
validates_presence_of :user
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
class LndhubAccount < LndhubBase
|
|
||||||
self.table_name = "accounts"
|
|
||||||
self.inheritance_column = :_type_disabled
|
|
||||||
|
|
||||||
has_many :ledgers, class_name: "LndhubAccountLedger",
|
|
||||||
foreign_key: "account_id"
|
|
||||||
|
|
||||||
belongs_to :user, class_name: "LndhubUser",
|
|
||||||
foreign_key: "user_id"
|
|
||||||
|
|
||||||
scope :current, -> { where(type: "current") }
|
|
||||||
scope :outgoing, -> { where(type: "outgoing") }
|
|
||||||
scope :incoming, -> { where(type: "incoming") }
|
|
||||||
scope :fees, -> { where(type: "fees") }
|
|
||||||
|
|
||||||
scope :with_balances, -> {
|
|
||||||
current.joins(:user).joins(:ledgers)
|
|
||||||
.group("accounts.id", "users.login")
|
|
||||||
.select("accounts.id, users.login, SUM(account_ledgers.amount) AS balance")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
class LndhubAccountLedger < LndhubBase
|
|
||||||
self.table_name = "account_ledgers"
|
|
||||||
end
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
class LndhubBase < ActiveRecord::Base
|
|
||||||
self.abstract_class = true
|
|
||||||
establish_connection :lndhub
|
|
||||||
end
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
class LndhubUser < LndhubBase
|
|
||||||
self.table_name = "users"
|
|
||||||
self.inheritance_column = :_type_disabled
|
|
||||||
|
|
||||||
has_many :accounts, class_name: "LndhubAccount",
|
|
||||||
foreign_key: "user_id"
|
|
||||||
|
|
||||||
belongs_to :user, class_name: "User",
|
|
||||||
primary_key: "ln_account",
|
|
||||||
foreign_key: "login"
|
|
||||||
|
|
||||||
def balance
|
|
||||||
accounts.current.first.ledgers.sum("account_ledgers.amount").to_i.abs
|
|
||||||
end
|
|
||||||
|
|
||||||
def sum_outgoing
|
|
||||||
accounts.outgoing.first.ledgers.sum("account_ledgers.amount").to_i.abs
|
|
||||||
end
|
|
||||||
|
|
||||||
def sum_incoming
|
|
||||||
accounts.incoming.first.ledgers.sum("account_ledgers.amount").to_i.abs
|
|
||||||
end
|
|
||||||
|
|
||||||
def sum_fees
|
|
||||||
accounts.fees.first.ledgers.sum("account_ledgers.amount").to_i.abs
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# RailsSettings Model
|
|
||||||
class Setting < RailsSettings::Base
|
|
||||||
cache_prefix { "v1" }
|
|
||||||
|
|
||||||
#
|
|
||||||
# Internal services
|
|
||||||
#
|
|
||||||
|
|
||||||
field :redis_url, type: :string, readonly: true,
|
|
||||||
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Registrations
|
|
||||||
#
|
|
||||||
|
|
||||||
field :reserved_usernames, type: :array, default: %w[
|
|
||||||
account accounts donations mail webmaster support
|
|
||||||
]
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sentry
|
|
||||||
#
|
|
||||||
|
|
||||||
field :sentry_enabled, type: :boolean, readonly: true,
|
|
||||||
default: (ENV["SENTRY_DSN"].present?.to_s || false)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Discourse
|
|
||||||
#
|
|
||||||
|
|
||||||
field :discourse_public_url, type: :string, readonly: true,
|
|
||||||
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :discourse_enabled, type: :boolean,
|
|
||||||
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
#
|
|
||||||
# ejabberd
|
|
||||||
#
|
|
||||||
|
|
||||||
field :ejabberd_enabled, type: :boolean,
|
|
||||||
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
field :ejabberd_api_url, type: :string, readonly: true,
|
|
||||||
default: ENV["EJABBERD_API_URL"].presence
|
|
||||||
|
|
||||||
field :ejabberd_admin_url, type: :string, readonly: true,
|
|
||||||
default: ENV["EJABBERD_ADMIN_URL"].presence
|
|
||||||
|
|
||||||
field :ejabberd_buddy_roster, type: :string,
|
|
||||||
default: "Buddies"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Gitea
|
|
||||||
#
|
|
||||||
|
|
||||||
field :gitea_public_url, type: :string, readonly: true,
|
|
||||||
default: ENV["GITEA_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :gitea_enabled, type: :boolean,
|
|
||||||
default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Lightning Network
|
|
||||||
#
|
|
||||||
|
|
||||||
field :lndhub_api_url, type: :string, readonly: true,
|
|
||||||
default: ENV["LNDHUB_API_URL"].presence
|
|
||||||
|
|
||||||
field :lndhub_enabled, type: :boolean,
|
|
||||||
default: (ENV["LNDHUB_API_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
field :lndhub_admin_enabled, type: :boolean,
|
|
||||||
default: (ENV["LNDHUB_ADMIN_UI"] || false)
|
|
||||||
|
|
||||||
field :lndhub_public_key, type: :string, readonly: true,
|
|
||||||
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
|
|
||||||
|
|
||||||
field :lndhub_keysend_enabled, type: :boolean,
|
|
||||||
default: -> { self.lndhub_public_key.present?.to_s || false }
|
|
||||||
|
|
||||||
#
|
|
||||||
# Mastodon
|
|
||||||
#
|
|
||||||
|
|
||||||
field :mastodon_public_url, type: :string, readonly: true,
|
|
||||||
default: ENV["MASTODON_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :mastodon_enabled, type: :boolean,
|
|
||||||
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
#
|
|
||||||
# MediaWiki
|
|
||||||
#
|
|
||||||
|
|
||||||
field :mediawiki_public_url, type: :string, readonly: true,
|
|
||||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :mediawiki_enabled, type: :boolean,
|
|
||||||
default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Nostr
|
|
||||||
#
|
|
||||||
|
|
||||||
field :nostr_enabled, type: :boolean, default: true
|
|
||||||
end
|
|
||||||
@@ -3,50 +3,28 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
# Relations
|
# Relations
|
||||||
has_many :invitations, dependent: :destroy
|
has_many :invitations, dependent: :destroy
|
||||||
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
|
||||||
has_one :inviter, through: :invitation, source: :user
|
|
||||||
has_many :invitees, through: :invitations
|
|
||||||
|
|
||||||
has_many :donations, dependent: :nullify
|
has_many :donations, dependent: :nullify
|
||||||
|
|
||||||
has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user",
|
|
||||||
primary_key: "ln_account", foreign_key: "login"
|
|
||||||
|
|
||||||
has_many :accounts, through: :lndhub_user
|
|
||||||
|
|
||||||
validates_uniqueness_of :cn
|
validates_uniqueness_of :cn
|
||||||
validates_length_of :cn, :minimum => 3
|
validates_length_of :cn, :minimum => 3
|
||||||
validates_format_of :cn, with: /\A([a-z0-9\-])*\z/,
|
|
||||||
if: Proc.new{ |u| u.cn.present? },
|
|
||||||
message: "is invalid. Please use only letters, numbers and -"
|
|
||||||
validates_format_of :cn, without: /\A-/,
|
|
||||||
if: Proc.new{ |u| u.cn.present? },
|
|
||||||
message: "is invalid. Usernames need to start with a letter."
|
|
||||||
validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i,
|
|
||||||
message: "has already been taken"
|
|
||||||
|
|
||||||
validates_uniqueness_of :email
|
validates_uniqueness_of :email
|
||||||
validates :email, email: true
|
validates :email, email: true
|
||||||
|
|
||||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
lockbox_encrypts :ln_login
|
||||||
scope :pending, -> { where(confirmed_at: nil) }
|
lockbox_encrypts :ln_password
|
||||||
|
|
||||||
has_encrypted :ln_login, :ln_password
|
|
||||||
|
|
||||||
# Include default devise modules. Others available are:
|
# Include default devise modules. Others available are:
|
||||||
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||||
devise :ldap_authenticatable,
|
devise :ldap_authenticatable,
|
||||||
:confirmable,
|
:confirmable,
|
||||||
:recoverable,
|
:recoverable,
|
||||||
:validatable,
|
:validatable
|
||||||
:timeoutable,
|
|
||||||
:rememberable
|
|
||||||
|
|
||||||
def ldap_before_save
|
def ldap_before_save
|
||||||
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
||||||
self.ou = dn.split(',')
|
|
||||||
.select{|e| e[0..1] == "ou"}.first
|
dn = Devise::LDAP::Adapter.get_ldap_param(self.cn, "dn")
|
||||||
.delete_prefix("ou=")
|
self.ou = dn.split(',').select{|e| e[0..1] == "ou"}.first.delete_prefix("ou=")
|
||||||
|
|
||||||
if self.confirmed_at.blank? && self.confirmation_token.blank?
|
if self.confirmed_at.blank? && self.confirmation_token.blank?
|
||||||
# User had an account with a trusted email address before akkounts was a thing
|
# User had an account with a trusted email address before akkounts was a thing
|
||||||
@@ -54,28 +32,11 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def devise_after_confirmation
|
|
||||||
enable_service %w[ discourse ejabberd gitea mediawiki ]
|
|
||||||
|
|
||||||
#TODO enable in development when we have easy setup of ejabberd etc.
|
|
||||||
return if Rails.env.development?
|
|
||||||
|
|
||||||
if inviter.present?
|
|
||||||
exchange_xmpp_contact_with_inviter if Setting.ejabberd_enabled?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_devise_notification(notification, *args)
|
|
||||||
devise_mailer.send(notification, self, *args).deliver_later
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_password(new_password, new_password_confirmation)
|
def reset_password(new_password, new_password_confirmation)
|
||||||
self.password = new_password
|
if new_password == new_password_confirmation && ::Devise.ldap_update_password
|
||||||
self.password_confirmation = new_password_confirmation
|
Devise::LDAP::Adapter.update_password(login_with, new_password)
|
||||||
return false unless valid?
|
end
|
||||||
|
clear_reset_password_token if valid?
|
||||||
Devise::LDAP::Adapter.update_password(login_with, new_password)
|
|
||||||
clear_reset_password_token
|
|
||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -101,48 +62,4 @@ class User < ApplicationRecord
|
|||||||
lndhub.authenticate self
|
lndhub.authenticate self
|
||||||
lndhub.addinvoice payload
|
lndhub.addinvoice payload
|
||||||
end
|
end
|
||||||
|
|
||||||
def dn
|
|
||||||
return @dn if defined?(@dn)
|
|
||||||
@dn = Devise::LDAP::Adapter.get_dn(self.cn)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ldap_entry
|
|
||||||
ldap.fetch_users(uid: self.cn, ou: self.ou).first
|
|
||||||
end
|
|
||||||
|
|
||||||
def services_enabled
|
|
||||||
ldap_entry[:service] || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def enable_service(service)
|
|
||||||
current_services = services_enabled
|
|
||||||
new_services = Array(service).map(&:to_s)
|
|
||||||
services = (current_services + new_services).uniq
|
|
||||||
ldap.replace_attribute(dn, :service, services)
|
|
||||||
end
|
|
||||||
|
|
||||||
def disable_service(service)
|
|
||||||
current_services = services_enabled
|
|
||||||
disabled_services = Array(service).map(&:to_s)
|
|
||||||
services = (current_services - disabled_services).uniq
|
|
||||||
ldap.replace_attribute(dn, :service, services)
|
|
||||||
end
|
|
||||||
|
|
||||||
def disable_all_services
|
|
||||||
ldap.delete_attribute(dn,:service)
|
|
||||||
end
|
|
||||||
|
|
||||||
def exchange_xmpp_contact_with_inviter
|
|
||||||
return unless inviter.services_enabled.include?("ejabberd") &&
|
|
||||||
services_enabled.include?("ejabberd")
|
|
||||||
XmppExchangeContactsJob.perform_later(inviter, self.cn, self.ou)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ldap
|
|
||||||
return @ldap_service if defined?(@ldap_service)
|
|
||||||
@ldap_service = LdapService.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ class CreateAccount < ApplicationService
|
|||||||
def call
|
def call
|
||||||
user = create_user_in_database
|
user = create_user_in_database
|
||||||
add_ldap_document
|
add_ldap_document
|
||||||
create_lndhub_account(user) if Setting.lndhub_enabled
|
create_lndhub_wallet(user)
|
||||||
|
|
||||||
if @invitation.present?
|
if @invitation.present?
|
||||||
update_invitation(user.id)
|
update_invitation(user.id)
|
||||||
|
exchange_xmpp_contacts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -42,9 +43,15 @@ class CreateAccount < ApplicationService
|
|||||||
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_lndhub_account(user)
|
def exchange_xmpp_contacts
|
||||||
|
#TODO enable in development when we have easy setup of ejabberd etc.
|
||||||
|
return if Rails.env.development?
|
||||||
|
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_lndhub_wallet(user)
|
||||||
#TODO enable in development when we have a local lndhub (mock?) API
|
#TODO enable in development when we have a local lndhub (mock?) API
|
||||||
return if Rails.env.development?
|
return if Rails.env.development?
|
||||||
CreateLndhubAccountJob.perform_later(user)
|
CreateLndhubWalletJob.perform_later(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,8 +17,4 @@ class EjabberdApiClient
|
|||||||
def add_rosteritem(payload)
|
def add_rosteritem(payload)
|
||||||
post "add_rosteritem", payload
|
post "add_rosteritem", payload
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_message(payload)
|
|
||||||
post "send_message", payload
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,18 +3,6 @@ class LdapService < ApplicationService
|
|||||||
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_attribute(dn, attr, values)
|
|
||||||
ldap_client.add_attribute dn, attr, values
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace_attribute(dn, attr, values)
|
|
||||||
ldap_client.replace_attribute dn, attr, values
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_attribute(dn, attr)
|
|
||||||
ldap_client.delete_attribute dn, attr
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_entry(dn, attrs, interactive=false)
|
def add_entry(dn, attrs, interactive=false)
|
||||||
puts "Adding entry: #{dn}" if interactive
|
puts "Adding entry: #{dn}" if interactive
|
||||||
res = ldap_client.add dn: dn, attributes: attrs
|
res = ldap_client.add dn: dn, attributes: attrs
|
||||||
@@ -22,6 +10,10 @@ class LdapService < ApplicationService
|
|||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_attribute(dn, attr, value)
|
||||||
|
ldap_client.add_attribute dn, attr, value
|
||||||
|
end
|
||||||
|
|
||||||
def delete_entry(dn, interactive=false)
|
def delete_entry(dn, interactive=false)
|
||||||
puts "Deleting entry: #{dn}" if interactive
|
puts "Deleting entry: #{dn}" if interactive
|
||||||
res = ldap_client.delete dn: dn
|
res = ldap_client.delete dn: dn
|
||||||
@@ -50,17 +42,18 @@ class LdapService < ApplicationService
|
|||||||
treebase = ldap_config["base"]
|
treebase = ldap_config["base"]
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes = %w{dn cn uid mail admin service}
|
attributes = %w{dn cn uid mail admin}
|
||||||
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
|
filter = Net::LDAP::Filter.eq("uid", "*")
|
||||||
|
|
||||||
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
entries.sort_by! { |e| e.cn[0] }
|
entries.sort_by! { |e| e.cn[0] }
|
||||||
|
|
||||||
entries = entries.collect do |e|
|
entries = entries.collect do |e|
|
||||||
{
|
{
|
||||||
uid: e.uid.first,
|
uid: e.uid.first,
|
||||||
mail: e.try(:mail) ? e.mail.first : nil,
|
mail: e.try(:mail) ? e.mail.first : nil,
|
||||||
admin: e.try(:admin) ? 'admin' : nil,
|
admin: e.try(:admin) ? 'admin' : nil
|
||||||
service: e.try(:service)
|
# password: e.userpassword.first
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -138,4 +131,5 @@ class LdapService < ApplicationService
|
|||||||
def ldap_config
|
def ldap_config
|
||||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,13 +28,8 @@ class Lndhub
|
|||||||
"Accept" => "application/json",
|
"Accept" => "application/json",
|
||||||
"Authorization" => "Bearer #{auth_token}"
|
"Authorization" => "Bearer #{auth_token}"
|
||||||
})
|
})
|
||||||
data = JSON.parse(res.body)
|
|
||||||
|
|
||||||
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
|
JSON.parse(res.body)
|
||||||
raise "BAD_AUTH"
|
|
||||||
else
|
|
||||||
data
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(payload)
|
def create(payload)
|
||||||
@@ -42,20 +37,20 @@ class Lndhub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def authenticate(user)
|
def authenticate(user)
|
||||||
credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
|
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
|
||||||
self.auth_token = credentials["access_token"]
|
self.auth_token = credentials["access_token"]
|
||||||
self.auth_token
|
self.auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def balance(user_token=nil)
|
def balance(user_token)
|
||||||
get "balance", user_token || auth_token
|
get "balance", user_token || auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def gettxs(user_token=nil)
|
def gettxs(user_token)
|
||||||
get "gettxs", user_token || auth_token
|
get "gettxs", user_token || auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def getuserinvoices(user_token=nil)
|
def getuserinvoices(user_token)
|
||||||
get "getuserinvoices", user_token || auth_token
|
get "getuserinvoices", user_token || auth_token
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
class LndhubV2
|
|
||||||
attr_accessor :auth_token
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@base_url = ENV["LNDHUB_API_URL"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def post(endpoint, payload, options={})
|
|
||||||
headers = { "Content-Type" => "application/json" }
|
|
||||||
if auth_token
|
|
||||||
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
|
|
||||||
elsif options[:admin_token]
|
|
||||||
headers.merge!({ "Authorization" => "Bearer #{options[:admin_token]}" })
|
|
||||||
end
|
|
||||||
|
|
||||||
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
|
|
||||||
|
|
||||||
if res.status != 200
|
|
||||||
Rails.logger.error "[lndhub] API request failed:"
|
|
||||||
Rails.logger.error res.body
|
|
||||||
#TODO add some kind of exception tracking/notifications
|
|
||||||
end
|
|
||||||
|
|
||||||
JSON.parse(res.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get(endpoint, auth_token)
|
|
||||||
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
|
||||||
"Content-Type" => "application/json",
|
|
||||||
"Accept" => "application/json",
|
|
||||||
"Authorization" => "Bearer #{auth_token}"
|
|
||||||
})
|
|
||||||
|
|
||||||
JSON.parse(res.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(payload)
|
|
||||||
post "create", payload
|
|
||||||
end
|
|
||||||
|
|
||||||
def authenticate(user)
|
|
||||||
credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
|
|
||||||
self.auth_token = credentials["access_token"]
|
|
||||||
self.auth_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def balance(user_token=nil)
|
|
||||||
get "balance", user_token || auth_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def gettxs(user_token)
|
|
||||||
get "gettxs", user_token || auth_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def getuserinvoices(user_token)
|
|
||||||
get "getuserinvoices", user_token || auth_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def addinvoice(payload)
|
|
||||||
invoice = post "addinvoice", {
|
|
||||||
amt: payload[:amount],
|
|
||||||
memo: payload[:memo],
|
|
||||||
description_hash: payload[:description_hash]
|
|
||||||
}
|
|
||||||
|
|
||||||
invoice["payment_request"]
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# V2
|
|
||||||
#
|
|
||||||
|
|
||||||
def create_account(payload={})
|
|
||||||
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_invoice(payload)
|
|
||||||
# Payload: { amount: 1000, description: "", description_hash: "" }
|
|
||||||
post "v2/invoices", payload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
<%= render HeaderComponent.new(title: "Admin Panel") %>
|
<%= render HeaderComponent.new(title: "Admin Panel") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<div class="text-center">
|
<p class="text-center">
|
||||||
<p class="my-12 inline-flex align-center items-center">
|
With great power comes great responsibility.
|
||||||
<%= image_tag("/img/illustrations/undraw_vault_re_s4my.svg", class: 'h-48') %>
|
</p>
|
||||||
</p>
|
|
||||||
<p class="text-gray-500">
|
|
||||||
With great power comes great responsibility.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,41 +1,58 @@
|
|||||||
<%= form_with(url: url, model: donation, local: true) do |form| %>
|
<%= form_with(url: url, model: donation, local: true) do |form| %>
|
||||||
<% if donation.errors.any? %>
|
<% if donation.errors.any? %>
|
||||||
<section id="error_explanation">
|
<div id="error_explanation">
|
||||||
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
|
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
|
||||||
<ul class="list-disc list-inside">
|
<ul>
|
||||||
<% donation.errors.full_messages.each do |message| %>
|
<% donation.errors.full_messages.each do |message| %>
|
||||||
<li><%= message %></li>
|
<li><%= message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<section class="sm:w-1/2 grid grid-cols-2 items-center gap-y-2">
|
<div class="field">
|
||||||
<%= form.label :user_id %>
|
<p>
|
||||||
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn, {} %>
|
<%= form.label :user_id %>
|
||||||
|
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
|
||||||
<%= form.label :amount_sats, "Amount BTC (sats)" %>
|
|
||||||
<%= form.number_field :amount_sats %>
|
|
||||||
|
|
||||||
<%= form.label :amount_eur, "Amount EUR (cents)" %>
|
|
||||||
<%= form.number_field :amount_eur %>
|
|
||||||
|
|
||||||
<%= form.label :amount_usd, "Amount USD (cents)"%>
|
|
||||||
<%= form.number_field :amount_usd %>
|
|
||||||
|
|
||||||
<%= form.label :public_name %>
|
|
||||||
<%= form.text_field :public_name %>
|
|
||||||
|
|
||||||
<%= form.label :paid_at %>
|
|
||||||
<%= form.text_field :paid_at %>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<p class="pt-6 border-t border-gray-200 text-right">
|
|
||||||
<%= link_to 'Cancel',
|
|
||||||
@donation.id.present? ? admin_donation_path(@donation) : admin_donations_path,
|
|
||||||
class: 'btn-md btn-gray' %>
|
|
||||||
<%= form.submit class: 'ml-2 btn-md btn-blue' %>
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<p>
|
||||||
|
<%= form.label :amount_sats, "Amount BTC (sats)" %>
|
||||||
|
<%= form.number_field :amount_sats %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<p>
|
||||||
|
<%= form.label :amount_eur, "Amount EUR (cents)" %>
|
||||||
|
<%= form.number_field :amount_eur %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<p>
|
||||||
|
<%= form.label :amount_usd, "Amount USD (cents)"%>
|
||||||
|
<%= form.number_field :amount_usd %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<p>
|
||||||
|
<%= form.label :public_name %>
|
||||||
|
<%= form.text_field :public_name %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<p>
|
||||||
|
<%= form.label :paid_at %>
|
||||||
|
<%= form.text_field :paid_at %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-8">
|
||||||
|
<%= form.submit class: 'btn-md btn-blue' %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
|
<%= render HeaderComponent.new(title: "Donations") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<h2>Editing Donation</h2>
|
||||||
|
|
||||||
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
|
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
|
||||||
|
|
||||||
|
<p class="mt-8">
|
||||||
|
<%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||||
|
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||||
|
<p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,27 +1,8 @@
|
|||||||
<%= render HeaderComponent.new(title: "Donations") %>
|
<%= render HeaderComponent.new(title: "Donations") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
|
||||||
<%= render QuickstatsContainerComponent.new do %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Overall',
|
|
||||||
value: @stats[:overall_sats],
|
|
||||||
unit: 'sats'
|
|
||||||
) %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Donors',
|
|
||||||
value: @stats[:donor_count],
|
|
||||||
meta: "/ #{User.count} users"
|
|
||||||
) %>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<% if @donations.any? %>
|
<% if @donations.any? %>
|
||||||
<h3>Recent Donations</h3>
|
<table>
|
||||||
<table class="divided mb-8">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>User</th>
|
<th>User</th>
|
||||||
@@ -30,35 +11,32 @@
|
|||||||
<th class="text-right">in USD</th>
|
<th class="text-right">in USD</th>
|
||||||
<th class="pl-2">Public name</th>
|
<th class="pl-2">Public name</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th></th>
|
<th colspan="3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @donations.each do |donation| %>
|
<% @donations.each do |donation| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= link_to donation.user.address, admin_user_path(donation.user.address), class: 'ks-text-link' %></td>
|
<td><%= donation.user.address %></td>
|
||||||
<td class="text-right"><%= sats_to_btc donation.amount_sats %></td>
|
<td class="text-right"><%= sats_to_btc donation.amount_sats %></td>
|
||||||
<td class="text-right"><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
<td class="text-right"><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
|
||||||
<td class="text-right"><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
<td class="text-right"><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
|
||||||
<td class="pl-2"><%= donation.public_name %></td>
|
<td class="pl-2"><%= donation.public_name %></td>
|
||||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") : "" %></td>
|
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
|
||||||
<td class="text-right">
|
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||||
<%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||||
<%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
<td><%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
||||||
<%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %></td>
|
||||||
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<%== pagy_nav @pagy %>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
No donations yet.
|
No donations yet.
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
|
||||||
|
|
||||||
<p class="mt-12">
|
<p class="mt-12">
|
||||||
<%= link_to 'Record an out-of-system donation', new_admin_donation_path, class: 'btn-md btn-gray' %>
|
<%= link_to 'Record an out-of-system donation', new_admin_donation_path, class: 'btn-md btn-gray' %>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<%= render HeaderComponent.new(title: "Add Donation") %>
|
<%= render HeaderComponent.new(title: "Donations") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<h2>New Donation</h2>
|
||||||
|
|
||||||
<%= render 'form', donation: @donation, url: admin_donations_path %>
|
<%= render 'form', donation: @donation, url: admin_donations_path %>
|
||||||
|
|
||||||
|
<p class="mt-8">
|
||||||
|
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,41 +1,38 @@
|
|||||||
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
|
<%= render HeaderComponent.new(title: "Donations") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<p>
|
||||||
<table class="w-1/2 divided">
|
<strong>User:</strong>
|
||||||
<tbody>
|
<%= @donation.user.address %>
|
||||||
<tr>
|
</p>
|
||||||
<th>User</th>
|
|
||||||
<td><%= link_to @donation.user.address, admin_user_path(@donation.user.address), class: 'ks-text-link' %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Amount sats</th>
|
|
||||||
<td><%= @donation.amount_sats %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Amount EUR</th>
|
|
||||||
<td><%= @donation.amount_eur %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Amount USD</th>
|
|
||||||
<td><%= @donation.amount_usd %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Public name</th>
|
|
||||||
<td><%= @donation.public_name %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<td><%= @donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<p>
|
||||||
<p class="pt-6 border-t border-gray-200 text-right">
|
<strong>Amount sats:</strong>
|
||||||
<%= link_to 'Back', admin_donations_path, class: 'btn-md btn-gray' %>
|
<%= @donation.amount_sats %>
|
||||||
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ml-2 btn-md btn-blue mr-1' %>
|
</p>
|
||||||
</p>
|
|
||||||
</section>
|
<p>
|
||||||
|
<strong>Amount eur:</strong>
|
||||||
|
<%= @donation.amount_eur %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Amount usd:</strong>
|
||||||
|
<%= @donation.amount_usd %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Public name:</strong>
|
||||||
|
<%= @donation.public_name %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>Date:</strong>
|
||||||
|
<%= @donation.paid_at %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="mt-8">
|
||||||
|
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||||
|
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,29 +2,17 @@
|
|||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<section>
|
||||||
<%= render QuickstatsContainerComponent.new do %>
|
<p>
|
||||||
<%= render QuickstatsItemComponent.new(
|
There are currently <strong><%= @invitations_unused_count %>
|
||||||
type: :number,
|
unused invitations</strong> available to existing users.
|
||||||
title: 'Available',
|
<strong><%= @users_with_referrals_count %> users</strong> have successfully
|
||||||
value: @stats[:available],
|
invited new users.
|
||||||
) %>
|
</p>
|
||||||
<%= 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>
|
</section>
|
||||||
<% if @invitations_used.any? %>
|
<% if @invitations_used.any? %>
|
||||||
<section>
|
<section>
|
||||||
<h3>Recently Accepted</h3>
|
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
||||||
<table class="divided mb-8">
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Token</th>
|
<th>Token</th>
|
||||||
@@ -38,13 +26,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
|
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
|
||||||
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||||
<td><%= link_to invitation.user.address, admin_user_path(invitation.user.address), class: "ks-text-link" %></td>
|
<td><%= invitation.user.address %></td>
|
||||||
<td><%= link_to invitation.invitee.address, admin_user_path(invitation.invitee.address), class: "ks-text-link" %></td>
|
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<%== pagy_nav @pagy %>
|
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
34
app/views/admin/ldap_users/index.html.erb
Normal file
34
app/views/admin/ldap_users/index.html.erb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "LDAP Users: #{@ou}") %>
|
||||||
|
|
||||||
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<h3 class="hidden">Domains</h3>
|
||||||
|
<ul class="mb-10">
|
||||||
|
<li class="inline-block">
|
||||||
|
<%= link_to 'kosmos.org', admin_ldap_users_path, class: "ks-text-link" %>
|
||||||
|
</li>
|
||||||
|
<li class="inline-block ml-6">
|
||||||
|
<%= link_to '5apps.com', admin_ldap_users_path(ou: '5apps.com'), class: "ks-text-link" %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>UID</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
<th>Admin</th>
|
||||||
|
<!-- <th>Password</th> -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @entries.each do |entry| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= entry[:uid] %></td>
|
||||||
|
<td><%= entry[:mail] %></td>
|
||||||
|
<td><%= entry[:admin] %></td>
|
||||||
|
<!-- <td><%= entry[:password] %></td> -->
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% end %>
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Lightning Network") %>
|
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
|
||||||
<section>
|
|
||||||
<%= render QuickstatsContainerComponent.new do %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Current user balance',
|
|
||||||
value: @ln[:current_balance],
|
|
||||||
unit: 'sats'
|
|
||||||
) %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Users with sats',
|
|
||||||
value: @ln[:users_with_sats],
|
|
||||||
meta: "/ #{User.count}"
|
|
||||||
) %>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Accounts</h3>
|
|
||||||
<table class="divided">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>LN Account</th>
|
|
||||||
<th>User</th>
|
|
||||||
<th>Balance</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<% @accounts.each do |account| %>
|
|
||||||
<tr>
|
|
||||||
<td class="font-mono">
|
|
||||||
<%= account.login %>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<% if user = @users.find{ |u| u[2] == account.login } %>
|
|
||||||
<%= link_to "#{user[0]}@#{user[1]}", admin_user_path("#{user[0]}@#{user[1]}"), class: "ks-text-link" %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
<td><%= number_with_delimiter account.balance.to_i.to_s %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<section>
|
|
||||||
<ul>
|
|
||||||
<% errors.full_messages.each do |msg| %>
|
|
||||||
<li><%= msg %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
|
||||||
<%= form_for(Setting.new, url: admin_settings_registrations_path) do |f| %>
|
|
||||||
<section>
|
|
||||||
<h3>Registrations</h3>
|
|
||||||
|
|
||||||
<% if @errors && @errors.any? %>
|
|
||||||
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<label class="block">
|
|
||||||
<p class="font-bold mb-1">Reserved usernames</p>
|
|
||||||
<p class="text-gray-500">
|
|
||||||
These usernames cannot be registered as accounts:
|
|
||||||
</p>
|
|
||||||
<%= f.text_area :reserved_usernames,
|
|
||||||
value: Setting.reserved_usernames.join("\n"),
|
|
||||||
class: "h-44 mb-2" %>
|
|
||||||
<p class="text-sm text-gray-500">
|
|
||||||
One username per line
|
|
||||||
</p>
|
|
||||||
</label>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<p class="pt-6 border-t border-gray-200 text-right">
|
|
||||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h3>Discourse</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :discourse_enabled,
|
|
||||||
enabled: Setting.discourse_enabled?,
|
|
||||||
title: "Enable Discourse integration",
|
|
||||||
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 %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<h3>ejabberd (XMPP)</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :ejabberd_enabled,
|
|
||||||
enabled: Setting.ejabberd_enabled?,
|
|
||||||
title: "Enable ejabberd integration",
|
|
||||||
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::FieldsetComponent.new(
|
|
||||||
title: "Contact roster name",
|
|
||||||
description: "Used when exchanging contacts after signup from invitation"
|
|
||||||
) do %>
|
|
||||||
<%= f.text_field :ejabberd_buddy_roster,
|
|
||||||
value: Setting.ejabberd_buddy_roster,
|
|
||||||
class: "w-full" %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h3>Gitea</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :gitea_enabled,
|
|
||||||
enabled: Setting.gitea_enabled?,
|
|
||||||
title: "Enable Gitea integration",
|
|
||||||
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 %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<h3>Lightning Network</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :lndhub_enabled,
|
|
||||||
enabled: Setting.lndhub_enabled?,
|
|
||||||
title: "Enable LNDHub integration",
|
|
||||||
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 %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h3>Mastodon</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :mastodon_enabled,
|
|
||||||
enabled: Setting.mastodon_enabled?,
|
|
||||||
title: "Enable Mastodon integration",
|
|
||||||
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 %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<h3>MediaWiki</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :mediawiki_enabled,
|
|
||||||
enabled: Setting.mediawiki_enabled?,
|
|
||||||
title: "Enable MediaWiki integration",
|
|
||||||
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 %>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<h3>Nostr</h3>
|
|
||||||
<ul role="list">
|
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
|
||||||
form: f,
|
|
||||||
attribute: :nostr_enabled,
|
|
||||||
enabled: Setting.nostr_enabled?,
|
|
||||||
title: "Enable Nostr integration (experimental)",
|
|
||||||
description: "Allow adding nostr pubkeys and resolve user addresses via NIP-05"
|
|
||||||
) %>
|
|
||||||
</ul>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
|
||||||
<%= form_for(Setting.new, url: admin_settings_services_path) do |f| %>
|
|
||||||
<%= hidden_field_tag :service, @service %>
|
|
||||||
|
|
||||||
<% if @errors && @errors.any? %>
|
|
||||||
<section>
|
|
||||||
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<%= render partial: @service, locals: { f: f } %>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<p class="pt-6 border-t border-gray-200 text-right">
|
|
||||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "Users: #{@ou}") %>
|
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
|
||||||
<section>
|
|
||||||
<%= render QuickstatsContainerComponent.new do %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Confirmed',
|
|
||||||
value: @stats[:users_confirmed],
|
|
||||||
) %>
|
|
||||||
<%= render QuickstatsItemComponent.new(
|
|
||||||
type: :number,
|
|
||||||
title: 'Pending',
|
|
||||||
value: @stats[:users_pending],
|
|
||||||
) %>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<% if @orgs.length > 1 %>
|
|
||||||
<section>
|
|
||||||
<h3 class="hidden">Domains</h3>
|
|
||||||
<ul>
|
|
||||||
<% @orgs.each do |org| %>
|
|
||||||
<li class="inline-block">
|
|
||||||
<%= link_to org[:ou], admin_users_path(ou: org[:ou]), class: "ks-text-link" %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<table class="divided mb-8">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>UID</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Roles</th>
|
|
||||||
<!-- <th>Password</th> -->
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<% @users.each do |user| %>
|
|
||||||
<tr>
|
|
||||||
<td><%= link_to(user.cn, admin_user_path(user.address), class: 'ks-text-link') %></td>
|
|
||||||
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
|
|
||||||
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<%== pagy_nav @pagy %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
<%= render HeaderComponent.new(title: "User: #{@user.address}") %>
|
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
|
||||||
<div class="mb-12 sm:flex sm:flex-row sm:gap-x-8">
|
|
||||||
<section class="sm:flex-1">
|
|
||||||
<h3>Account</h3>
|
|
||||||
<table class="divided">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Created at</th>
|
|
||||||
<td><%= @user.created_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Confirmed at</th>
|
|
||||||
<td>
|
|
||||||
<% if @user.confirmed_at %>
|
|
||||||
<%= @user.confirmed_at.strftime("%Y-%m-%d (%H:%M UTC)") %>
|
|
||||||
<% else %>
|
|
||||||
<%= badge "pending", :yellow %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Email</th>
|
|
||||||
<td><%= @user.email %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Roles</th>
|
|
||||||
<td><%= @user.is_admin? ? badge("admin", :red) : "—" %></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Invited by</th>
|
|
||||||
<td>
|
|
||||||
<% if @user.inviter %>
|
|
||||||
<%= link_to @user.inviter.address, admin_user_path(@user.inviter.address), class: 'ks-text-link' %>
|
|
||||||
<% else %>—<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Invitations available</th>
|
|
||||||
<td>
|
|
||||||
<%= @user.invitations.count %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th class="align-top">Invited users</th>
|
|
||||||
<td class="align-top">
|
|
||||||
<% if @user.invitees.length > 0 %>
|
|
||||||
<ul class="mb-0">
|
|
||||||
<% @user.invitees.order(cn: :asc).each do |invitee| %>
|
|
||||||
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.address, admin_user_path(invitee.address), class: 'ks-text-link' %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
<% else %>—<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="sm:flex-1 sm:pt-0">
|
|
||||||
<!-- <h3>Actions</h3> -->
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h3>Services</h3>
|
|
||||||
<table class="divided">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Enabled</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<% if Setting.discourse_enabled %>
|
|
||||||
<tr>
|
|
||||||
<td>Discourse</td>
|
|
||||||
<td>
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @services_enabled.include?("discourse"),
|
|
||||||
input_enabled: false
|
|
||||||
) %>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<%= link_to "Open profile", "#{Setting.discourse_public_url}/u/#{@user.cn}/summary", class: "btn-sm btn-gray" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.gitea_enabled %>
|
|
||||||
<tr>
|
|
||||||
<td>Gitea</td>
|
|
||||||
<td>
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @services_enabled.include?("gitea"),
|
|
||||||
input_enabled: false
|
|
||||||
) %>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<%= link_to "Open profile", "#{Setting.gitea_public_url}/#{@user.cn}", class: "btn-sm btn-gray" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.mastodon_enabled %>
|
|
||||||
<tr>
|
|
||||||
<td>Mastodon</td>
|
|
||||||
<td>
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @services_enabled.include?("mastodon"),
|
|
||||||
input_enabled: false
|
|
||||||
) %>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<%= link_to "Open profile", "#{Setting.mastodon_public_url}/@#{@user.cn}", class: "btn-sm btn-gray" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.mediawiki_enabled %>
|
|
||||||
<tr>
|
|
||||||
<td>MediaWiki</td>
|
|
||||||
<td>
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @services_enabled.include?("mediawiki"),
|
|
||||||
input_enabled: false
|
|
||||||
) %>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<%= link_to "Open profile", "#{Setting.mediawiki_public_url}/Special:Contributions/#{@user.cn}", class: "btn-sm btn-gray" %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.ejabberd_enabled %>
|
|
||||||
<tr>
|
|
||||||
<td>XMPP (ejabberd)</td>
|
|
||||||
<td>
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: @services_enabled.include?("ejabberd"),
|
|
||||||
input_enabled: false
|
|
||||||
) %>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<% if Setting.ejabberd_admin_url.present? %>
|
|
||||||
<%= link_to "Open profile", "#{Setting.ejabberd_admin_url}/server/#{@user.ou}/user/#{@user.cn}/", class: "btn-sm btn-gray" %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<% if Setting.lndhub_admin_enabled? && @user.confirmed? %>
|
|
||||||
<section>
|
|
||||||
<h3>LndHub</h3>
|
|
||||||
<% if @lndhub_user %>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account</th>
|
|
||||||
<th>Balance</th>
|
|
||||||
<th>Incoming</th>
|
|
||||||
<th>Outgoing</th>
|
|
||||||
<th>Fees</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><%= @user.ln_account %></td>
|
|
||||||
<td><%= number_with_delimiter @lndhub_user.balance %> sats</td>
|
|
||||||
<td><%= number_with_delimiter @lndhub_user.sum_incoming %> sats</td>
|
|
||||||
<td><%= number_with_delimiter @lndhub_user.sum_outgoing %> sats</td>
|
|
||||||
<td><%= number_with_delimiter @lndhub_user.sum_fees %> sats</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<% else %>
|
|
||||||
<p>No LndHub user found for account <strong class="font-mono"><%= @user.ln_account %></strong>.
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<%= @body %>
|
|
||||||
@@ -2,87 +2,60 @@
|
|||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<section>
|
||||||
<p class="mb-8">
|
<p>
|
||||||
Your Kosmos account and password currently give you access to these
|
Your Kosmos account and password currently give you access to these
|
||||||
services:
|
services:
|
||||||
</p>
|
</p>
|
||||||
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 services mt-12">
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-cover bg-[center_top_-50px] bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_xmpp.svg)]">
|
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
||||||
<%= link_to "https://wiki.kosmos.org/Services:Chat",
|
</h3>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
<p class="text-gray-500">
|
||||||
<h3 class="mb-3.5">Chat</h3>
|
Chat rooms and instant messaging (XMPP/Jabber)
|
||||||
<p class="text-gray-600">
|
</p>
|
||||||
Federated chat rooms and instant messaging
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-[length:95%] bg-center bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
<%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %>
|
||||||
<%= link_to "https://community.kosmos.org",
|
</h3>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
<p class="text-gray-500">
|
||||||
<h3 class="mb-3.5">Discourse</h3>
|
Kosmos community forums and user support/help site
|
||||||
<p class="text-gray-600">
|
</p>
|
||||||
Kosmos community forums and user support/help site
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-cover bg-[center_top_-20px] bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_mediawiki.svg)]">
|
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 inline" } %>
|
||||||
<%= link_to "https://wiki.kosmos.org",
|
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
</h3>
|
||||||
<h3 class="mb-3.5">Wiki</h3>
|
<p class="text-gray-500">
|
||||||
<p class="text-gray-600">
|
Send and receive sats over the Bitcoin Lightning Network
|
||||||
Kosmos documentation and knowledge base
|
</p>
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_lightning.svg)]">
|
<%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %>
|
||||||
<%= link_to wallet_path,
|
</h3>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
<p class="text-gray-500">
|
||||||
<h3 class="mb-3.5">Wallet</h3>
|
Kosmos documentation and knowledge base
|
||||||
<p class="text-gray-600">
|
</p>
|
||||||
Send and receive sats over the Bitcoin Lightning Network
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-cover bg-center bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_gitea.png)]">
|
<%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %>
|
||||||
<%= link_to "https://gitea.kosmos.org",
|
</h3>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
<p class="text-gray-500">
|
||||||
<h3 class="mb-3.5">Gitea</h3>
|
Code hosting and collaboration for software projects
|
||||||
<p class="text-gray-600">
|
</p>
|
||||||
Code hosting and collaboration for software projects
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div>
|
||||||
bg-cover bg-[center_top_-70px] bg-no-repeat
|
<h3 class="mb-3.5">
|
||||||
bg-[url(/img/logos/icon_droneci.svg)]">
|
<%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %>
|
||||||
<%= link_to "https://drone.kosmos.org",
|
</h3>
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
<p class="text-gray-500">
|
||||||
<h3 class="mb-3.5">Drone CI</h3>
|
Continuous integration for software projects on Gitea
|
||||||
<p class="text-gray-600">
|
</p>
|
||||||
Continuous integration for software projects on Gitea
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <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 "https://kosmos.social", class: "block h-full px-6 py-6 rounded-md" do %> -->
|
|
||||||
<!-- <h3 class="mb-3.5">Mastodon</h3> -->
|
|
||||||
<!-- <p class="text-gray-400"> -->
|
|
||||||
<!-- Your account on the Open Social Web -->
|
|
||||||
<!-- </p> -->
|
|
||||||
<!-- <% end %> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= render "devise/shared/error_messages", resource: resource %>
|
||||||
<p>
|
<p>
|
||||||
<%= f.label :email, 'Email address', class: 'block mb-2 font-bold' %>
|
<%= f.label :email, 'Email address', class: 'block mb-1 w-full' %>
|
||||||
<%= f.email_field :email,
|
<%= f.email_field :email,
|
||||||
required: true, autofocus: true, autocomplete: "email",
|
required: true, autofocus: true, autocomplete: "email",
|
||||||
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
|
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p class="mt-8">
|
<p class="mt-8">
|
||||||
<%= f.submit "Resend confirmation link",
|
<%= f.submit "Resend confirmation link",
|
||||||
class: 'btn-md btn-blue w-full' %>
|
class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<%= f.label :password, "New password" %>
|
<%= f.label :password, "New password" %>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "w-full" %>
|
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
|
||||||
<% if @minimum_password_length %>
|
<% if @minimum_password_length %>
|
||||||
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
|
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -20,10 +20,10 @@
|
|||||||
<%= f.label :password_confirmation, "Confirm new password" %>
|
<%= f.label :password_confirmation, "Confirm new password" %>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "w-full" %>
|
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-8">
|
<p class="mt-8">
|
||||||
<%= f.submit "Change my password", class: 'btn-md btn-blue w-full' %>
|
<%= f.submit "Change my password", class: 'btn-md btn-blue' %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -5,21 +5,19 @@
|
|||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
|
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= render "devise/shared/error_messages", resource: resource %>
|
||||||
<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",
|
|
||||||
required: true, class: "relative grow"%>
|
|
||||||
<span class="relative shrink-0 text-gray-500">@ kosmos.org</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p>
|
<p>
|
||||||
<%= f.label :email, 'Email address', class: 'block mb-2 font-bold' %>
|
<%= f.label :cn, 'User', class: 'block' %>
|
||||||
|
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
|
||||||
|
required: true, class: "w-full md:w-3/5"%>
|
||||||
|
<span class="ml-1 text-gray-500">@ kosmos.org</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= f.label :email, 'Email address', class: 'block' %>
|
||||||
<%= f.email_field :email, autocomplete: "email", required: true,
|
<%= f.email_field :email, autocomplete: "email", required: true,
|
||||||
class: "w-full"%>
|
class: "w-full md:w-3/5"%>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-8">
|
<p class="mt-8">
|
||||||
<%= f.submit "Send me a reset link", class: 'btn-md btn-blue w-full' %>
|
<%= f.submit "Send me a reset link", class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -3,47 +3,21 @@
|
|||||||
<%= render MainCompactComponent.new do %>
|
<%= render MainCompactComponent.new do %>
|
||||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||||
<%= render "devise/shared/error_messages", resource: resource %>
|
<%= render "devise/shared/error_messages", resource: resource %>
|
||||||
<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",
|
|
||||||
required: true, class: "relative grow", tabindex: "1" %>
|
|
||||||
<span class="relative shrink-0 text-gray-500">@ kosmos.org</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<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" %>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<%= tag.div class: "flex items-center mb-8 gap-x-3", data: {
|
|
||||||
controller: "settings--toggle",
|
|
||||||
:'settings--toggle-switch-enabled-value' => "false"
|
|
||||||
} do %>
|
|
||||||
<div class="relative inline-flex flex-shrink-0">
|
|
||||||
<%= render FormElements::ToggleComponent.new(
|
|
||||||
enabled: false, input_enabled: true, class_names: "hidden",
|
|
||||||
tabindex: "3", data: {
|
|
||||||
:'settings--toggle-target' => "button",
|
|
||||||
action: "settings--toggle#toggleSwitch"
|
|
||||||
}) %>
|
|
||||||
<%= f.check_box :remember_me, {
|
|
||||||
checked: false,
|
|
||||||
data: { :'settings--toggle-target' => "checkbox" }
|
|
||||||
}, "true", "false" %>
|
|
||||||
</div>
|
|
||||||
<%= f.label :remember_me,
|
|
||||||
class: "text-gray-500 flex flex-col",
|
|
||||||
data: { action: "click->settings--toggle#toggleSwitch" } %>
|
|
||||||
<p class="grow text-sm text-right">
|
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name),
|
|
||||||
class: "text-gray-500 underline" %><br />
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<%= f.submit "Log in", class: 'btn-md btn-blue w-full', tabindex: "4" %>
|
<%= f.label :cn, 'User', class: 'block' %>
|
||||||
|
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
|
||||||
|
class: "w-full md:w-3/5"%>
|
||||||
|
<span class="ml-1 text-gray-500">@ kosmos.org</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%= f.label :password, class: 'block' %>
|
||||||
|
<%= f.password_field :password, autocomplete: "current-password",
|
||||||
|
class: "w-full md:w-3/5"%>
|
||||||
|
</p>
|
||||||
|
<p class="mt-8">
|
||||||
|
<%= f.submit "Log in", class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= render "devise/shared/links" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
<div class="devise-links mt-8 text-sm">
|
<div class="devise-links mt-8 text-sm">
|
||||||
<%- if controller_name != 'sessions' %>
|
<%- if controller_name != 'sessions' %>
|
||||||
<p class="mb-2">
|
<p class="mb-1.5">
|
||||||
<%= link_to "Log in", new_session_path(resource_name),
|
<%= link_to "Log in", new_session_path(resource_name) %><br />
|
||||||
class: "text-gray-500 underline" %>
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||||
<p class="mb-2">
|
<p class="mb-1.5">
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name),
|
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
||||||
class: "text-gray-500 underline" %>
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %>
|
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||||
<p class="mb-2">
|
<p class="mb-1.5">
|
||||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name),
|
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
||||||
class: "text-gray-500 underline" %>
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||||
<p class="mb-2">
|
<p class="mb-1.5">
|
||||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name),
|
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
|
||||||
class: "text-gray-500 underline" %>
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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-grid <%= custom_class %>"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></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-grid"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
|
||||||
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 404 B |
@@ -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-x <%= custom_class %>"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></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-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||||
|
Before Width: | Height: | Size: 320 B After Width: | Height: | Size: 299 B |
@@ -6,13 +6,14 @@
|
|||||||
<p class="mb-8">
|
<p class="mb-8">
|
||||||
Invite your friends to a Kosmos account by sharing an invitation URL with them:
|
Invite your friends to a Kosmos account by sharing an invitation URL with them:
|
||||||
</p>
|
</p>
|
||||||
<ul class="md:w-3/4">
|
<ul>
|
||||||
<% @invitations_unused.each do |invitation| %>
|
<% @invitations_unused.each do |invitation| %>
|
||||||
<li class="font-mono mb-2 flex gap-1" data-controller="clipboard">
|
<li class="font-mono mb-1 flex gap-1 md:block"
|
||||||
<input type="text" disabled class="relative grow"
|
data-controller="clipboard">
|
||||||
|
<input type="text" disabled class="md:w-3/4 flex-1"
|
||||||
value="<%= invitation_url(invitation.token) %>"
|
value="<%= invitation_url(invitation.token) %>"
|
||||||
data-clipboard-target="source" />
|
data-clipboard-target="source" />
|
||||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0 w-auto"
|
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
|
||||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||||
title="Copy to clipboard">
|
title="Copy to clipboard">
|
||||||
<span class="content-initial">
|
<span class="content-initial">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user