Merge pull request 'Add user page to admin panel, improve other admin pages' (#88) from feature/admin_user_details into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #88
Reviewed-by: galfert <garret.alfert@gmail.com>
This commit is contained in:
Râu Cao 2023-02-26 14:16:41 +00:00
commit 7fd564726f
26 changed files with 267 additions and 130 deletions

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

@ -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,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,33 @@
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
@users = User.where(ou: @ou).order(cn: :asc).to_a
@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
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

@ -10,5 +10,12 @@ 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

View File

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

View File

@ -0,0 +1,2 @@
module UsersHelper
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

@ -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",
@ -66,6 +70,12 @@ class User < ApplicationRecord
end
end
def ldap_entry
return @ldap_entry if defined?(@ldap_entry)
ldap = LdapService.new
@ldap_entry = ldap.fetch_users(uid: self.cn, ou: self.ou).first
end
def address
"#{self.cn}@#{self.ou}"
end

View File

@ -43,7 +43,7 @@ class LdapService < ApplicationService
end
attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*")
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] }

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">
<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>

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">
<thead>
<tr>
<th>Token</th>
@ -38,8 +38,8 @@
<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>

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,22 +30,21 @@
<% end %>
<section>
<table>
<table class="divided">
<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>

View File

@ -0,0 +1,89 @@
<%= render HeaderComponent.new(title: "User: #{@user.address}") %>
<%= render MainSimpleComponent.new do %>
<section>
<h3>Account</h3>
<table class="w-1/2 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>
<% @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>
<% if Setting.lndhub_admin_enabled? %>
<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

@ -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,

View File

@ -39,7 +39,7 @@ Rails.application.routes.draw do
namespace :admin do
root to: 'dashboard#index'
get 'ldap_users', to: 'ldap_users#index'
resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
get 'invitations', to: 'invitations#index'
resources :donations
get 'lightning', to: 'lightning#index'

View File

@ -7,6 +7,15 @@ module.exports = {
'./app/helpers/**/*.rb',
'./app/javascript/**/*.js'
],
safelist: [
'bg-gray-100', 'text-gray-800',
'bg-red-100', 'text-red-800',
'bg-yellow-100', 'text-yellow-800',
'bg-green-100', 'text-green-800',
'bg-blue-100', 'text-blue-800',
'bg-purple-100', 'text-purple-800',
'bg-pink-100', 'text-pink-800',
],
theme: {
extend: {
fontFamily: {
@ -16,5 +25,5 @@ module.exports = {
},
plugins: [
require('@tailwindcss/forms')
],
]
}