Merge branch 'master' into feature/ln_address_keysend
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
2023-03-02 15:49:13 +00:00
57 changed files with 1042 additions and 214 deletions

View File

@@ -4,7 +4,9 @@
@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";

View File

@@ -36,10 +36,18 @@
@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;
}

View File

@@ -1,6 +1,6 @@
@layer components {
.btn {
@apply font-semibold rounded-md leading-none cursor-pointer text-center
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
transition-colors duration-75 focus:outline-none focus:ring-4;
}

View File

@@ -0,0 +1,5 @@
@layer components {
.services > div > a {
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%);
}
}

View File

@@ -0,0 +1,45 @@
@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;
}
}

View File

@@ -7,16 +7,30 @@
@apply text-left;
}
table th {
table thead 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 {
table td, tbody th {
@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;
}
}

View File

@@ -1,4 +1,5 @@
class Admin::BaseController < ApplicationController
include Pagy::Backend
before_action :authenticate_user!
before_action :authorize_admin
@@ -7,5 +8,4 @@ class Admin::BaseController < ApplicationController
def set_context
@context = :admin
end
end

View File

@@ -5,7 +5,8 @@ class Admin::DonationsController < Admin::BaseController
# GET /donations
# GET /donations.json
def index
@donations = Donation.all.order('created_at desc')
@pagy, @donations = pagy(Donation.all.order('created_at desc'))
@stats = {
overall_sats: @donations.all.sum("amount_sats"),
donor_count: Donation.distinct.count(:user_id)

View File

@@ -1,7 +1,8 @@
class Admin::InvitationsController < Admin::BaseController
def index
@current_section = :invitations
@invitations_used = Invitation.used.order('used_at desc')
@pagy, @invitations_used = pagy(Invitation.used.order('used_at desc'))
@stats = {
available: Invitation.unused.count,
accepted: @invitations_used.length,

View File

@@ -1,20 +0,0 @@
class Admin::LdapUsersController < Admin::BaseController
before_action :set_current_section
def index
ldap = LdapService.new
@ou = params[:ou] || "kosmos.org"
@orgs = ldap.fetch_organizations
@entries = ldap.fetch_users(ou: @ou)
@stats = {
users_confirmed: User.where(ou: @ou).confirmed.count,
users_pending: User.where(ou: @ou).pending.count
}
end
private
def set_current_section
@current_section = :ldap_users
end
end

View File

@@ -0,0 +1,35 @@
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

View File

@@ -0,0 +1,17 @@
# 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

View File

@@ -7,7 +7,7 @@ class WalletController < ApplicationController
before_action :fetch_balance
def index
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
qrcode = RQRCode::QRCode.new(@wallet_url)
@svg = qrcode.as_svg(

View File

@@ -1,4 +1,6 @@
module ApplicationHelper
include Pagy::Frontend
def sats_to_btc(sats)
sats.to_f / 100000000
end
@@ -10,5 +12,10 @@ 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
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

View File

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

View File

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

View File

@@ -2,13 +2,12 @@ class CreateLndhubAccountJob < ApplicationJob
queue_as :default
def perform(user)
return if user.ln_login.present? && user.ln_password.present?
return if user.ln_account.present? && user.ln_password.present?
lndhub = LndhubV2.new
credentials = lndhub.create_account
user.update! ln_account: credentials["login"],
ln_login: credentials["login"], # TODO remove when production is migrated
ln_password: credentials["password"]
end
end

View File

@@ -1,6 +1,7 @@
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

View File

@@ -10,6 +10,18 @@ class LndhubUser < LndhubBase
foreign_key: "login"
def balance
accounts.current.first.ledgers.sum("account_ledgers.amount")
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

View File

@@ -7,7 +7,7 @@ class Setting < RailsSettings::Base
#
field :reserved_usernames, type: :array, default: %w[
account accounts admin donations mail webmaster support
account accounts donations mail webmaster support
]
#

View File

@@ -3,6 +3,10 @@ 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",
@@ -27,8 +31,7 @@ class User < ApplicationRecord
scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :pending, -> { where(confirmed_at: nil) }
lockbox_encrypts :ln_login
lockbox_encrypts :ln_password
has_encrypted :ln_login, :ln_password
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
@@ -39,9 +42,9 @@ class User < ApplicationRecord
def ldap_before_save
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
dn = Devise::LDAP::Adapter.get_ldap_param(self.cn, "dn")
self.ou = dn.split(',').select{|e| e[0..1] == "ou"}.first.delete_prefix("ou=")
self.ou = dn.split(',')
.select{|e| e[0..1] == "ou"}.first
.delete_prefix("ou=")
if self.confirmed_at.blank? && self.confirmation_token.blank?
# User had an account with a trusted email address before akkounts was a thing
@@ -49,6 +52,10 @@ class User < ApplicationRecord
end
end
def devise_after_confirmation
enable_service %w[discourse gitea wiki xmpp]
end
def reset_password(new_password, new_password_confirmation)
self.password = new_password
self.password_confirmation = new_password_confirmation
@@ -81,4 +88,42 @@ 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
private
def ldap
return @ldap_service if defined?(@ldap_service)
@ldap_service = LdapService.new
end
end

View File

@@ -3,6 +3,18 @@ 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
@@ -10,10 +22,6 @@ 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
@@ -42,18 +50,17 @@ class LdapService < ApplicationService
treebase = ldap_config["base"]
end
attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*")
attributes = %w{dn cn uid mail admin service}
filter = Net::LDAP::Filter.eq("uid", args[: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
# password: e.userpassword.first
admin: e.try(:admin) ? 'admin' : nil,
service: e.try(:service)
}
end
end
@@ -131,5 +138,4 @@ class LdapService < ApplicationService
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
end

View File

@@ -2,7 +2,7 @@ class Lndhub
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_LEGACY_API_URL"]
@base_url = ENV["LNDHUB_API_URL"]
end
def post(endpoint, payload)
@@ -42,7 +42,7 @@ class Lndhub
end
def authenticate(user)
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end

View File

@@ -39,7 +39,7 @@ class LndhubV2
end
def authenticate(user)
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end

View File

@@ -10,46 +10,24 @@
</div>
<% end %>
<div class="field">
<p>
<%= form.label :user_id %>
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
</p>
</div>
<div class="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, {} %>
<div class="field">
<p>
<%= form.label :amount_sats, "Amount BTC (sats)" %>
<%= form.number_field :amount_sats %>
</p>
</div>
<%= form.label :amount_sats, "Amount BTC (sats)" %>
<%= form.number_field :amount_sats %>
<div class="field">
<p>
<%= form.label :amount_eur, "Amount EUR (cents)" %>
<%= form.number_field :amount_eur %>
</p>
</div>
<%= form.label :amount_eur, "Amount EUR (cents)" %>
<%= form.number_field :amount_eur %>
<div class="field">
<p>
<%= form.label :amount_usd, "Amount USD (cents)"%>
<%= form.number_field :amount_usd %>
</p>
</div>
<%= form.label :amount_usd, "Amount USD (cents)"%>
<%= form.number_field :amount_usd %>
<div class="field">
<p>
<%= form.label :public_name %>
<%= form.text_field :public_name %>
</p>
</div>
<%= form.label :public_name %>
<%= form.text_field :public_name %>
<div class="field">
<p>
<%= form.label :paid_at %>
<%= form.text_field :paid_at %>
</p>
<%= form.label :paid_at %>
<%= form.text_field :paid_at %>
</div>
<p class="mt-8">

View File

@@ -1,12 +1,9 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
<%= 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' %>
<%= link_to 'Cancel', admin_donation_path(@donation), class: 'btn-sm btn-gray' %>
<p>
<% end %>

View File

@@ -21,7 +21,7 @@
<section>
<% if @donations.any? %>
<h3>Recent Donations</h3>
<table>
<table class="divided mb-8">
<thead>
<tr>
<th>User</th>
@@ -33,11 +33,10 @@
<th></th>
</tr>
</thead>
<tbody>
<% @donations.each do |donation| %>
<tr>
<td><%= donation.user.address %></td>
<td><%= link_to donation.user.address, admin_user_path(donation.user.address), class: 'ks-text-link' %></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>
@@ -53,6 +52,7 @@
<% end %>
</tbody>
</table>
<%== pagy_nav @pagy %>
<% else %>
<p>
No donations yet.

View File

@@ -1,8 +1,6 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render HeaderComponent.new(title: "Add Donation") %>
<%= render MainSimpleComponent.new do %>
<h2>New Donation</h2>
<%= render 'form', donation: @donation, url: admin_donations_path %>
<p class="mt-8">

View File

@@ -1,38 +1,41 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render HeaderComponent.new(title: "Donation ##{@donation.id}") %>
<%= render MainSimpleComponent.new do %>
<p>
<strong>User:</strong>
<%= @donation.user.address %>
</p>
<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>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>
<section>
<p>
<%= link_to 'Edit', edit_admin_donation_path(@donation), class: 'btn-md btn-blue mr-1' %>
<%= link_to 'Back', admin_donations_path, class: 'btn-md btn-gray' %>
</p>
</section>
<% end %>

View File

@@ -24,7 +24,7 @@
<% if @invitations_used.any? %>
<section>
<h3>Recently Accepted</h3>
<table>
<table class="divided mb-8">
<thead>
<tr>
<th>Token</th>
@@ -38,12 +38,13 @@
<tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
<td><%= invitation.user.address %></td>
<td><%= User.find(invitation.invited_user_id).address %></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>
</tr>
<% end %>
</tbody>
</table>
<%== pagy_nav @pagy %>
</section>
<% end %>
<% end %>

View File

@@ -20,7 +20,7 @@
<section>
<h3>Accounts</h3>
<table>
<table class="divided">
<thead>
<tr>
<th>LN Account</th>
@@ -36,7 +36,7 @@
</td>
<td>
<% if user = @users.find{ |u| u[2] == account.login } %>
<%= "#{user[0]}@#{user[1]}" %>
<%= 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>

View File

@@ -22,14 +22,14 @@
<%= f.text_area :reserved_usernames,
value: Setting.reserved_usernames.join("\n"),
class: "h-44 mb-2" %>
<p class="mb-0 text-sm text-gray-500">
<p class="text-sm text-gray-500">
One username per line
</p>
</label>
</section>
<section>
<p class="mb-0 pt-6 border-t border-gray-200">
<p class="pt-6 border-t border-gray-200">
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
</p>
</section>

View File

@@ -18,7 +18,7 @@
<li class="flex items-center justify-between py-6">
<div class="flex flex-col">
<label class="font-bold mb-1">Enable LNDHub integration</label>
<p class="text-gray-500 mb-0">LNDHub configuration present and wallet features enabled</p>
<p class="text-gray-500">LNDHub configuration present and wallet features enabled</p>
</div>
<%= f.check_box :lndhub_enabled, checked: Setting.lndhub_enabled?,
disabled: true,
@@ -27,7 +27,7 @@
<li class="flex items-center justify-between py-6">
<div class="flex flex-col">
<label class="font-bold mb-1">Enable LNDHub admin panel</label>
<p class="text-gray-500 mb-0">LNDHub database configuration present and admin panel enabled</p>
<p class="text-gray-500">LNDHub database configuration present and admin panel enabled</p>
</div>
<%= f.check_box :lndhub_admin_enabled, checked: Setting.lndhub_admin_enabled?,
disabled: true,

View File

@@ -1,4 +1,4 @@
<%= render HeaderComponent.new(title: "LDAP Users: #{@ou}") %>
<%= render HeaderComponent.new(title: "Users: #{@ou}") %>
<%= render MainSimpleComponent.new do %>
<section>
@@ -22,7 +22,7 @@
<ul>
<% @orgs.each do |org| %>
<li class="inline-block">
<%= link_to org[:ou], admin_ldap_users_path(ou: org[:ou]), class: "ks-text-link" %>
<%= link_to org[:ou], admin_users_path(ou: org[:ou]), class: "ks-text-link" %>
</li>
<% end %>
</ul>
@@ -30,25 +30,25 @@
<% end %>
<section>
<table>
<table class="divided mb-8">
<thead>
<tr>
<th>UID</th>
<th>E-Mail</th>
<th>Admin</th>
<th>Status</th>
<th>Roles</th>
<!-- <th>Password</th> -->
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<% @users.each do |user| %>
<tr>
<td><%= entry[:uid] %></td>
<td><%= entry[:mail] %></td>
<td><%= entry[:admin] %></td>
<!-- <td><%= entry[:password] %></td> -->
<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 %>

View File

@@ -0,0 +1,123 @@
<%= 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 %>&mdash;<% 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 %>&mdash;<% 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="sm:w-1/4">
<tbody>
<tr>
<td>Discourse</td>
<td><%= check_box_tag 'service_discourse', 'enabled', @services_enabled.include?("discourse"), disabled: true %></td>
</tr>
<tr>
<td>Gitea</td>
<td><%= check_box_tag 'service_gitea', 'enabled', @services_enabled.include?("gitea"), disabled: true %></td>
</tr>
<tr>
<td>Mastodon</td>
<td><%= check_box_tag 'service_mastodon', 'enabled', @services_enabled.include?("mastodon"), disabled: true %></td>
</tr>
<tr>
<td>Wiki</td>
<td><%= check_box_tag 'service_wiki', 'enabled', @services_enabled.include?("wiki"), disabled: true %></td>
</tr>
<tr>
<td>XMPP</td>
<td><%= check_box_tag 'service_xmpp', 'enabled', @services_enabled.include?("xmpp"), disabled: true %></td>
</tr>
</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 %>

View File

@@ -2,60 +2,87 @@
<%= render MainSimpleComponent.new do %>
<section>
<p>
<p class="mb-8">
Your Kosmos account and password currently give you access to these
services:
</p>
<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 class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to "https://wiki.kosmos.org/Services:Chat",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600">
Federated chat rooms and instant messaging
</p>
<% end %>
</div>
<div>
<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 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>
<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 class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to "https://wiki.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600">
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
<div>
<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 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>
<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 class="border border-gray-300 rounded-md hover:border-gray-400
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to "https://gitea.kosmos.org",
class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600">
Code hosting and collaboration for software projects
</p>
<% end %>
</div>
<div>
<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 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>
<!-- <div class="border border&#45;gray&#45;300 rounded&#45;md hover:border&#45;gray&#45;400 -->
<!-- bg&#45;[length:80%] bg&#45;[right_top_&#45;30px] bg&#45;no&#45;repeat -->
<!-- bg&#45;[url(/img/logos/icon_mastodon.svg)]"> -->
<!-- <%= link_to "https://kosmos.social", class: "block h&#45;full px&#45;6 py&#45;6 rounded&#45;md" do %> -->
<!-- <h3 class="mb&#45;3.5">Mastodon</h3> -->
<!-- <p class="text&#45;gray&#45;400"> -->
<!-- Your account on the Open Social Web -->
<!-- </p> -->
<!-- <% end %> -->
<!-- </div> -->
</div>
</section>
<% end %>

View File

@@ -1,7 +1,7 @@
<%= link_to "Dashboard", admin_root_path,
class: main_nav_class(@current_section, :dashboard) %>
<%= link_to "Users", admin_ldap_users_path,
class: main_nav_class(@current_section, :ldap_users) %>
<%= link_to "Users", admin_users_path,
class: main_nav_class(@current_section, :users) %>
<%= link_to "Invitations", admin_invitations_path,
class: main_nav_class(@current_section, :invitations) %>
<%= link_to "Donations", admin_donations_path,