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
|
||||
- yarn install
|
||||
- rake css:build
|
||||
- bundle exec rspec
|
||||
- rake spec
|
||||
- name: rebuild-cache
|
||||
image: drillster/drone-volume-cache
|
||||
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_PORT=389
|
||||
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'
|
||||
|
||||
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
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'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3026'
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
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
|
||||
FROM ruby:2.7.6
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||
ldap-utils tini
|
||||
RUN apt-get update -qq && apt-get install -y curl ldap-utils
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
WORKDIR /akkounts
|
||||
COPY Gemfile /akkounts/Gemfile
|
||||
COPY Gemfile.lock /akkounts/Gemfile.lock
|
||||
@@ -17,5 +12,11 @@ RUN gem install foreman
|
||||
RUN npm install -g yarn
|
||||
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
|
||||
|
||||
# Configure the main process to run when running the image
|
||||
CMD ["bin", "dev"]
|
||||
|
||||
8
Gemfile
8
Gemfile
@@ -32,14 +32,12 @@ gem 'lockbox'
|
||||
|
||||
# Authentication
|
||||
gem 'warden'
|
||||
gem 'devise', '~> 4.9.0'
|
||||
gem 'devise'
|
||||
gem 'devise_ldap_authenticatable'
|
||||
gem 'net-ldap'
|
||||
|
||||
# Utilities
|
||||
gem "rqrcode", "~> 2.0"
|
||||
gem 'rails-settings-cached', '~> 2.8.3'
|
||||
gem 'pagy', '~> 6.0', '>= 6.0.2'
|
||||
|
||||
# HTTP requests
|
||||
gem 'faraday'
|
||||
@@ -48,10 +46,6 @@ gem 'faraday'
|
||||
gem 'sidekiq', '< 7'
|
||||
gem 'sidekiq-scheduler'
|
||||
|
||||
# Monitoring
|
||||
gem "sentry-ruby"
|
||||
gem "sentry-rails"
|
||||
|
||||
group :development, :test do
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3', '~> 1.4'
|
||||
|
||||
23
Gemfile.lock
23
Gemfile.lock
@@ -95,7 +95,7 @@ GEM
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
devise (4.9.0)
|
||||
devise (4.8.1)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
@@ -178,7 +178,6 @@ GEM
|
||||
nokogiri (1.13.9-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
pagy (6.0.2)
|
||||
pg (1.2.3)
|
||||
public_suffix (5.0.0)
|
||||
puma (4.3.12)
|
||||
@@ -207,9 +206,6 @@ GEM
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.3)
|
||||
loofah (~> 2.3)
|
||||
rails-settings-cached (2.8.3)
|
||||
activerecord (>= 5.0.0)
|
||||
railties (>= 5.0.0)
|
||||
railties (7.0.4)
|
||||
actionpack (= 7.0.4)
|
||||
activesupport (= 7.0.4)
|
||||
@@ -226,9 +222,9 @@ GEM
|
||||
redis-client (0.11.2)
|
||||
connection_pool
|
||||
regexp_parser (2.6.1)
|
||||
responders (3.1.0)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rexml (3.2.5)
|
||||
rqrcode (2.1.2)
|
||||
chunky_png (~> 1.0)
|
||||
@@ -254,11 +250,6 @@ GEM
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.8.2)
|
||||
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)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
@@ -320,7 +311,7 @@ DEPENDENCIES
|
||||
capybara
|
||||
cssbundling-rails
|
||||
database_cleaner
|
||||
devise (~> 4.9.0)
|
||||
devise
|
||||
devise_ldap_authenticatable
|
||||
dotenv-rails
|
||||
factory_bot_rails
|
||||
@@ -333,15 +324,11 @@ DEPENDENCIES
|
||||
listen (~> 3.2)
|
||||
lockbox
|
||||
net-ldap
|
||||
pagy (~> 6.0, >= 6.0.2)
|
||||
pg (~> 1.2.3)
|
||||
puma (~> 4.1)
|
||||
rails (~> 7.0.2)
|
||||
rails-settings-cached (~> 2.8.3)
|
||||
rqrcode (~> 2.0)
|
||||
rspec-rails
|
||||
sentry-rails
|
||||
sentry-ruby
|
||||
sidekiq (< 7)
|
||||
sidekiq-scheduler
|
||||
sprockets-rails
|
||||
|
||||
@@ -14,12 +14,12 @@ so:
|
||||
|
||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||
Docker Desktop)
|
||||
2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml`
|
||||
2. Uncomment the `web` section in `docker-compose.yml`
|
||||
3. Run `docker compose up` and wait until 389ds announces its successful start
|
||||
in the log output
|
||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||
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
|
||||
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
|
||||
|
||||
### Rails
|
||||
|
||||
* [Ruby on Rails](https://guides.rubyonrails.org/)
|
||||
* [Pagination](https://ddnexus.github.io/pagy/)
|
||||
* [Sass](https://sass-lang.com/documentation)
|
||||
|
||||
### Front-end
|
||||
|
||||
* [Tailwind CSS](https://tailwindcss.com/)
|
||||
* [Sass](https://sass-lang.com/documentation)
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
|
||||
@import "components/base";
|
||||
@import "components/buttons";
|
||||
@import "components/dashboard_services";
|
||||
@import "components/forms";
|
||||
@import "components/links";
|
||||
@import "components/notifications";
|
||||
@import "components/pagination";
|
||||
@import "components/tables";
|
||||
|
||||
@@ -36,18 +36,10 @@
|
||||
@apply mb-4 leading-6;
|
||||
}
|
||||
|
||||
main p:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
main ul {
|
||||
@apply mb-6;
|
||||
}
|
||||
|
||||
main ul:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
main ul li {
|
||||
@apply leading-6;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@layer components {
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
input[type=text], input[type=email], input[type=password],
|
||||
input[type=number], select, textarea {
|
||||
@apply rounded-md bg-gray-100 focus:bg-white
|
||||
input[type=number], select {
|
||||
@apply mt-1 rounded-md bg-gray-100 focus:bg-white
|
||||
border-transparent focus:border-transparent focus:ring-2
|
||||
focus:ring-blue-600 focus:ring-opacity-75;
|
||||
}
|
||||
@@ -10,10 +10,6 @@
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
.field_with_errors input {
|
||||
@apply w-full bg-red-100;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
@apply text-red-700;
|
||||
}
|
||||
|
||||
@@ -5,4 +5,10 @@
|
||||
&:visited { @apply text-indigo-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;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
table th {
|
||||
@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 td:not(:last-of-type) {
|
||||
@apply pr-2;
|
||||
}
|
||||
|
||||
table td, tbody th {
|
||||
table td {
|
||||
@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
|
||||
|
||||
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
|
||||
@level = level
|
||||
@path = path
|
||||
@icon = icon
|
||||
@active = active
|
||||
@@ -13,15 +12,12 @@ class SidenavLinkComponent < ViewComponent::Base
|
||||
end
|
||||
|
||||
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
|
||||
"#{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
|
||||
"#{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
|
||||
"#{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
|
||||
|
||||
|
||||
@@ -7,14 +7,10 @@
|
||||
<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">
|
||||
<% if @balance %>
|
||||
<span class="text-2xl"><%= number_with_delimiter @balance %></span>
|
||||
<span class="text-xl">sats</span>
|
||||
<br>
|
||||
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
|
||||
<span class="text-sm text-gray-500">Available balance</span>
|
||||
<% else %>
|
||||
<span class="text-2xl">n/a</span>
|
||||
<span class="text-xl">sats</span>
|
||||
<br>
|
||||
<span class="text-xl">n/a sats</span><br>
|
||||
<span class="text-sm text-gray-500">Balance unavailable</span>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
class Admin::BaseController < ApplicationController
|
||||
include Pagy::Backend
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :authorize_admin
|
||||
@@ -8,4 +7,5 @@ class Admin::BaseController < ApplicationController
|
||||
def set_context
|
||||
@context = :admin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -5,12 +5,7 @@ class Admin::DonationsController < Admin::BaseController
|
||||
# GET /donations
|
||||
# GET /donations.json
|
||||
def index
|
||||
@pagy, @donations = pagy(Donation.all.order('created_at desc'))
|
||||
|
||||
@stats = {
|
||||
overall_sats: @donations.all.sum("amount_sats"),
|
||||
donor_count: Donation.distinct.count(:user_id)
|
||||
}
|
||||
@donations = Donation.all
|
||||
end
|
||||
|
||||
# GET /donations/1
|
||||
@@ -34,14 +29,10 @@ class Admin::DonationsController < Admin::BaseController
|
||||
|
||||
respond_to do |format|
|
||||
if @donation.save
|
||||
format.html do
|
||||
redirect_to admin_donation_url(@donation), flash: {
|
||||
success: 'Donation was successfully created.'
|
||||
}
|
||||
end
|
||||
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
|
||||
format.json { render :show, status: :created, location: @donation }
|
||||
else
|
||||
format.html { render :new, status: :unprocessable_entity }
|
||||
format.html { render :new }
|
||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
@@ -52,14 +43,10 @@ class Admin::DonationsController < Admin::BaseController
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @donation.update(donation_params)
|
||||
format.html do
|
||||
redirect_to admin_donation_url(@donation), flash: {
|
||||
success: 'Donation was successfully updated.'
|
||||
}
|
||||
end
|
||||
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
|
||||
format.json { render :show, status: :ok, location: @donation }
|
||||
else
|
||||
format.html { render :edit, status: :unprocessable_entity }
|
||||
format.html { render :edit }
|
||||
format.json { render json: @donation.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
@@ -70,10 +57,7 @@ class Admin::DonationsController < Admin::BaseController
|
||||
def destroy
|
||||
@donation.destroy
|
||||
respond_to do |format|
|
||||
format.html do redirect_to admin_donations_url, flash: {
|
||||
success: 'Donation was successfully destroyed.'
|
||||
}
|
||||
end
|
||||
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
class Admin::InvitationsController < Admin::BaseController
|
||||
def index
|
||||
@current_section = :invitations
|
||||
@pagy, @invitations_used = pagy(Invitation.used.order('used_at desc'))
|
||||
|
||||
@stats = {
|
||||
available: Invitation.unused.count,
|
||||
accepted: @invitations_used.length,
|
||||
users_with_referrals: Invitation.used.distinct.count(:user_id)
|
||||
}
|
||||
@invitations_unused_count = Invitation.unused.count
|
||||
@users_with_referrals_count = Invitation.used.distinct.count(:user_id)
|
||||
@invitations_used = Invitation.used.order('used_at desc')
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
unless user_signed_in?
|
||||
redirect_to welcome_path and return
|
||||
|
||||
@@ -5,7 +5,7 @@ class InvitationsController < ApplicationController
|
||||
# GET /invitations
|
||||
def index
|
||||
@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
|
||||
end
|
||||
|
||||
@@ -27,10 +27,7 @@ class InvitationsController < ApplicationController
|
||||
|
||||
respond_to do |format|
|
||||
if @invitation.save
|
||||
format.html do redirect_to @invitation, flash: {
|
||||
success: 'Invitation was successfully created.'
|
||||
}
|
||||
end
|
||||
format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' }
|
||||
format.json { render :show, status: :created, location: @invitation }
|
||||
else
|
||||
format.html { render :new }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
class LnurlpayController < ApplicationController
|
||||
before_action :check_feature_enabled
|
||||
before_action :find_user_by_address
|
||||
|
||||
MIN_SATS = 10
|
||||
@@ -18,20 +17,6 @@ class LnurlpayController < ApplicationController
|
||||
}
|
||||
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
|
||||
amount = params[:amount].to_i / 1000 # msats
|
||||
address = params[:address]
|
||||
@@ -47,7 +32,7 @@ class LnurlpayController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
memo = "To #{address}"
|
||||
memo = "Sats for #{address}"
|
||||
memo = "#{memo}: \"#{comment}\"" if comment.present?
|
||||
|
||||
payment_request = @user.ln_create_invoice({
|
||||
@@ -87,9 +72,4 @@ class LnurlpayController < ApplicationController
|
||||
comment.length <= MAX_COMMENT_CHARS
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_feature_enabled
|
||||
http_status :not_found unless Setting.lndhub_enabled?
|
||||
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
|
||||
|
||||
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)
|
||||
@svg = qrcode.as_svg(
|
||||
@@ -28,13 +28,13 @@ class WalletController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def authenticate_with_lndhub(options={})
|
||||
if session[:ln_auth_token].present? && !options[:force_reauth]
|
||||
@ln_auth_token = session[:ln_auth_token]
|
||||
def authenticate_with_lndhub
|
||||
if session["ln_auth_token"].present?
|
||||
@ln_auth_token = session["ln_auth_token"]
|
||||
else
|
||||
lndhub = Lndhub.new
|
||||
auth_token = lndhub.authenticate(current_user)
|
||||
session[:ln_auth_token] = auth_token
|
||||
session["ln_auth_token"] = auth_token
|
||||
@ln_auth_token = auth_token
|
||||
end
|
||||
rescue
|
||||
@@ -49,23 +49,14 @@ class WalletController < ApplicationController
|
||||
lndhub = Lndhub.new
|
||||
data = lndhub.balance @ln_auth_token
|
||||
@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
|
||||
|
||||
def fetch_transactions
|
||||
lndhub = Lndhub.new
|
||||
txs = lndhub.gettxs @ln_auth_token
|
||||
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||
|
||||
process_transactions(txs + invoices)
|
||||
rescue
|
||||
authenticate_with_lndhub(force_reauth: true)
|
||||
return [] if @fetch_transactions_retried
|
||||
@fetch_transactions_retried = true
|
||||
fetch_transactions
|
||||
end
|
||||
|
||||
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
|
||||
include Pagy::Frontend
|
||||
|
||||
def sats_to_btc(sats)
|
||||
sats.to_f / 100000000
|
||||
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"
|
||||
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
|
||||
|
||||
|
||||
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"]
|
||||
|
||||
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"));
|
||||
|
||||
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
|
||||
|
||||
def perform(inviter, username, domain)
|
||||
@@ -7,12 +7,12 @@ class XmppExchangeContactsJob < ApplicationJob
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": username, "localhost": domain,
|
||||
"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({
|
||||
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||
"user": username, "host": domain,
|
||||
"nick": username, "group": Setting.ejabberd_buddy_roster, "subs": "both"
|
||||
"nick": username, "group": "Friends", "subs": "both"
|
||||
})
|
||||
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
|
||||
default from: 'from@example.com'
|
||||
layout 'mailer'
|
||||
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
|
||||
|
||||
# Validations
|
||||
validates_presence_of :user
|
||||
validates_presence_of :amount_sats
|
||||
validates_presence_of :paid_at
|
||||
|
||||
# Hooks
|
||||
# TODO before_create :store_fiat_value
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class Invitation < ApplicationRecord
|
||||
# Relations
|
||||
belongs_to :user
|
||||
belongs_to :invitee, class_name: "User", foreign_key: 'invited_user_id', optional: true
|
||||
|
||||
# Validations
|
||||
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
|
||||
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_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_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 :email, email: true
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
scope :pending, -> { where(confirmed_at: nil) }
|
||||
|
||||
has_encrypted :ln_login, :ln_password
|
||||
lockbox_encrypts :ln_login
|
||||
lockbox_encrypts :ln_password
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||
devise :ldap_authenticatable,
|
||||
:confirmable,
|
||||
:recoverable,
|
||||
:validatable,
|
||||
:timeoutable,
|
||||
:rememberable
|
||||
:validatable
|
||||
|
||||
def ldap_before_save
|
||||
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
||||
self.ou = dn.split(',')
|
||||
.select{|e| e[0..1] == "ou"}.first
|
||||
.delete_prefix("ou=")
|
||||
|
||||
dn = Devise::LDAP::Adapter.get_ldap_param(self.cn, "dn")
|
||||
self.ou = dn.split(',').select{|e| e[0..1] == "ou"}.first.delete_prefix("ou=")
|
||||
|
||||
if self.confirmed_at.blank? && self.confirmation_token.blank?
|
||||
# User had an account with a trusted email address before akkounts was a thing
|
||||
@@ -54,28 +32,11 @@ class User < ApplicationRecord
|
||||
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)
|
||||
self.password = new_password
|
||||
self.password_confirmation = new_password_confirmation
|
||||
return false unless valid?
|
||||
|
||||
Devise::LDAP::Adapter.update_password(login_with, new_password)
|
||||
clear_reset_password_token
|
||||
if new_password == new_password_confirmation && ::Devise.ldap_update_password
|
||||
Devise::LDAP::Adapter.update_password(login_with, new_password)
|
||||
end
|
||||
clear_reset_password_token if valid?
|
||||
save
|
||||
end
|
||||
|
||||
@@ -101,48 +62,4 @@ class User < ApplicationRecord
|
||||
lndhub.authenticate self
|
||||
lndhub.addinvoice payload
|
||||
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
|
||||
|
||||
@@ -11,10 +11,11 @@ class CreateAccount < ApplicationService
|
||||
def call
|
||||
user = create_user_in_database
|
||||
add_ldap_document
|
||||
create_lndhub_account(user) if Setting.lndhub_enabled
|
||||
create_lndhub_wallet(user)
|
||||
|
||||
if @invitation.present?
|
||||
update_invitation(user.id)
|
||||
exchange_xmpp_contacts
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,9 +43,15 @@ class CreateAccount < ApplicationService
|
||||
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
|
||||
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
|
||||
return if Rails.env.development?
|
||||
CreateLndhubAccountJob.perform_later(user)
|
||||
CreateLndhubWalletJob.perform_later(user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,8 +17,4 @@ class EjabberdApiClient
|
||||
def add_rosteritem(payload)
|
||||
post "add_rosteritem", payload
|
||||
end
|
||||
|
||||
def send_message(payload)
|
||||
post "send_message", payload
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,18 +3,6 @@ class LdapService < ApplicationService
|
||||
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
||||
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)
|
||||
puts "Adding entry: #{dn}" if interactive
|
||||
res = ldap_client.add dn: dn, attributes: attrs
|
||||
@@ -22,6 +10,10 @@ class LdapService < ApplicationService
|
||||
res
|
||||
end
|
||||
|
||||
def add_attribute(dn, attr, value)
|
||||
ldap_client.add_attribute dn, attr, value
|
||||
end
|
||||
|
||||
def delete_entry(dn, interactive=false)
|
||||
puts "Deleting entry: #{dn}" if interactive
|
||||
res = ldap_client.delete dn: dn
|
||||
@@ -50,17 +42,18 @@ class LdapService < ApplicationService
|
||||
treebase = ldap_config["base"]
|
||||
end
|
||||
|
||||
attributes = %w{dn cn uid mail admin service}
|
||||
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
|
||||
attributes = %w{dn cn uid mail admin}
|
||||
filter = Net::LDAP::Filter.eq("uid", "*")
|
||||
|
||||
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,
|
||||
service: e.try(:service)
|
||||
admin: e.try(:admin) ? 'admin' : nil
|
||||
# password: e.userpassword.first
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -138,4 +131,5 @@ class LdapService < ApplicationService
|
||||
def ldap_config
|
||||
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -28,13 +28,8 @@ class Lndhub
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
})
|
||||
data = JSON.parse(res.body)
|
||||
|
||||
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
|
||||
raise "BAD_AUTH"
|
||||
else
|
||||
data
|
||||
end
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def create(payload)
|
||||
@@ -42,20 +37,20 @@ class Lndhub
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def balance(user_token=nil)
|
||||
def balance(user_token)
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def gettxs(user_token=nil)
|
||||
def gettxs(user_token)
|
||||
get "gettxs", user_token || auth_token
|
||||
end
|
||||
|
||||
def getuserinvoices(user_token=nil)
|
||||
def getuserinvoices(user_token)
|
||||
get "getuserinvoices", user_token || auth_token
|
||||
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 MainSimpleComponent.new do %>
|
||||
<div class="text-center">
|
||||
<p class="my-12 inline-flex align-center items-center">
|
||||
<%= image_tag("/img/illustrations/undraw_vault_re_s4my.svg", class: 'h-48') %>
|
||||
</p>
|
||||
<p class="text-gray-500">
|
||||
With great power comes great responsibility.
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
With great power comes great responsibility.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
<%= form_with(url: url, model: donation, local: true) do |form| %>
|
||||
<% 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>
|
||||
<ul class="list-disc list-inside">
|
||||
<ul>
|
||||
<% donation.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<section class="sm:w-1/2 grid grid-cols-2 items-center gap-y-2">
|
||||
<%= 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' %>
|
||||
<div class="field">
|
||||
<p>
|
||||
<%= form.label :user_id %>
|
||||
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
|
||||
</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 %>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
|
||||
<%= render HeaderComponent.new(title: "Donations") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<h2>Editing Donation</h2>
|
||||
|
||||
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Show', admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
<p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
<%= render HeaderComponent.new(title: "Donations") %>
|
||||
|
||||
<%= 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? %>
|
||||
<h3>Recent Donations</h3>
|
||||
<table class="divided mb-8">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
@@ -30,35 +11,32 @@
|
||||
<th class="text-right">in USD</th>
|
||||
<th class="pl-2">Public name</th>
|
||||
<th>Date</th>
|
||||
<th></th>
|
||||
<th colspan="3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @donations.each do |donation| %>
|
||||
<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"><% 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="pl-2"><%= donation.public_name %></td>
|
||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d (%H:%M UTC)") : "" %></td>
|
||||
<td class="text-right">
|
||||
<%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
||||
<%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %>
|
||||
<%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
||||
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
|
||||
</td>
|
||||
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
|
||||
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
|
||||
<td><%= link_to 'Destroy', admin_donation_path(donation), class: 'btn btn-sm btn-red',
|
||||
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%== pagy_nav @pagy %>
|
||||
<% else %>
|
||||
<p>
|
||||
No donations yet.
|
||||
</p>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<p class="mt-12">
|
||||
<%= 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 %>
|
||||
<h2>New Donation</h2>
|
||||
|
||||
<%= render 'form', donation: @donation, url: admin_donations_path %>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
|
||||
<%= render HeaderComponent.new(title: "Donations") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<table class="w-1/2 divided">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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>
|
||||
<p>
|
||||
<strong>User:</strong>
|
||||
<%= @donation.user.address %>
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<p class="pt-6 border-t border-gray-200 text-right">
|
||||
<%= link_to 'Back', admin_donations_path, class: 'btn-md btn-gray' %>
|
||||
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ml-2 btn-md btn-blue mr-1' %>
|
||||
</p>
|
||||
</section>
|
||||
<p>
|
||||
<strong>Amount sats:</strong>
|
||||
<%= @donation.amount_sats %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Amount eur:</strong>
|
||||
<%= @donation.amount_eur %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Amount usd:</strong>
|
||||
<%= @donation.amount_usd %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Public name:</strong>
|
||||
<%= @donation.public_name %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Date:</strong>
|
||||
<%= @donation.paid_at %>
|
||||
</p>
|
||||
|
||||
<p class="mt-8">
|
||||
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'ks-text-link' %> |
|
||||
<%= link_to 'Back', admin_donations_path, class: 'ks-text-link' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
@@ -2,29 +2,17 @@
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<%= render QuickstatsContainerComponent.new do %>
|
||||
<%= render QuickstatsItemComponent.new(
|
||||
type: :number,
|
||||
title: 'Available',
|
||||
value: @stats[:available],
|
||||
) %>
|
||||
<%= 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 %>
|
||||
<p>
|
||||
There are currently <strong><%= @invitations_unused_count %>
|
||||
unused invitations</strong> available to existing users.
|
||||
<strong><%= @users_with_referrals_count %> users</strong> have successfully
|
||||
invited new users.
|
||||
</p>
|
||||
</section>
|
||||
<% if @invitations_used.any? %>
|
||||
<section>
|
||||
<h3>Recently Accepted</h3>
|
||||
<table class="divided mb-8">
|
||||
<h3>Accepted (<%= @invitations_used.length %>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
@@ -38,13 +26,12 @@
|
||||
<tr>
|
||||
<td class="overflow-ellipsis font-mono"><%= invitation.token %></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><%= link_to invitation.invitee.address, admin_user_path(invitation.invitee.address), class: "ks-text-link" %></td>
|
||||
<td><%= invitation.user.address %></td>
|
||||
<td><%= User.find(invitation.invited_user_id).address %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%== pagy_nav @pagy %>
|
||||
</section>
|
||||
<% 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 %>
|
||||
<section>
|
||||
<p class="mb-8">
|
||||
<p>
|
||||
Your Kosmos account and password currently give you access to these
|
||||
services:
|
||||
</p>
|
||||
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-50px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_xmpp.svg)]">
|
||||
<%= link_to "https://wiki.kosmos.org/Services:Chat",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Chat</h3>
|
||||
<p class="text-gray-600">
|
||||
Federated chat rooms and instant messaging
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 services mt-12">
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Chat rooms and instant messaging (XMPP/Jabber)
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:95%] bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||
<%= link_to "https://community.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos community forums and user support/help site
|
||||
</p>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Discourse", "https://community.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Kosmos community forums and user support/help site
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-20px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_mediawiki.svg)]">
|
||||
<%= link_to "https://wiki.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Wiki</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 inline" } %>
|
||||
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Send and receive sats over the Bitcoin Lightning Network
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_lightning.svg)]">
|
||||
<%= link_to wallet_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Wallet</h3>
|
||||
<p class="text-gray-600">
|
||||
Send and receive sats over the Bitcoin Lightning Network
|
||||
</p>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Wiki", "https://wiki.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_gitea.png)]">
|
||||
<%= link_to "https://gitea.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Gitea</h3>
|
||||
<p class="text-gray-600">
|
||||
Code hosting and collaboration for software projects
|
||||
</p>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Gitea", "https://gitea.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Code hosting and collaboration for software projects
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-70px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_droneci.svg)]">
|
||||
<%= link_to "https://drone.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Drone CI</h3>
|
||||
<p class="text-gray-600">
|
||||
Continuous integration for software projects on Gitea
|
||||
</p>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="mb-3.5">
|
||||
<%= link_to "Drone CI", "https://drone.kosmos.org", class: "ks-text-link" %>
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Continuous integration for software projects on Gitea
|
||||
</p>
|
||||
</div>
|
||||
<!-- <div 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>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
<p>
|
||||
<%= f.label :email, 'Email address', class: 'block mb-2 font-bold' %>
|
||||
<%= f.label :email, 'Email address', class: 'block mb-1 w-full' %>
|
||||
<%= f.email_field :email,
|
||||
required: true, autofocus: true, autocomplete: "email",
|
||||
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
|
||||
@@ -14,7 +14,7 @@
|
||||
</p>
|
||||
<p class="mt-8">
|
||||
<%= f.submit "Resend confirmation link",
|
||||
class: 'btn-md btn-blue w-full' %>
|
||||
class: 'btn-md btn-blue w-full sm:w-auto' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%= f.label :password, "New password" %>
|
||||
</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 %>
|
||||
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
|
||||
<% end %>
|
||||
@@ -20,10 +20,10 @@
|
||||
<%= f.label :password_confirmation, "Confirm new password" %>
|
||||
</p>
|
||||
<p>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "w-full" %>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</p>
|
||||
<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>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -5,21 +5,19 @@
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= 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>
|
||||
<%= 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,
|
||||
class: "w-full"%>
|
||||
class: "w-full md:w-3/5"%>
|
||||
</p>
|
||||
<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>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -3,47 +3,21 @@
|
||||
<%= render MainCompactComponent.new do %>
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||
<%= 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>
|
||||
<%= 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>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
<div class="devise-links mt-8 text-sm">
|
||||
<%- if controller_name != 'sessions' %>
|
||||
<p class="mb-2">
|
||||
<%= link_to "Log in", new_session_path(resource_name),
|
||||
class: "text-gray-500 underline" %>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Log in", new_session_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||
<p class="mb-2">
|
||||
<%= link_to "Forgot your password?", new_password_path(resource_name),
|
||||
class: "text-gray-500 underline" %>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %>
|
||||
<p class="mb-2">
|
||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name),
|
||||
class: "text-gray-500 underline" %>
|
||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||
<p class="mb-2">
|
||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name),
|
||||
class: "text-gray-500 underline" %>
|
||||
<p class="mb-1.5">
|
||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
|
||||
</p>
|
||||
<% end %>
|
||||
</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">
|
||||
Invite your friends to a Kosmos account by sharing an invitation URL with them:
|
||||
</p>
|
||||
<ul class="md:w-3/4">
|
||||
<ul>
|
||||
<% @invitations_unused.each do |invitation| %>
|
||||
<li class="font-mono mb-2 flex gap-1" data-controller="clipboard">
|
||||
<input type="text" disabled class="relative grow"
|
||||
<li class="font-mono mb-1 flex gap-1 md:block"
|
||||
data-controller="clipboard">
|
||||
<input type="text" disabled class="md:w-3/4 flex-1"
|
||||
value="<%= invitation_url(invitation.token) %>"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0 w-auto"
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<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