Compare commits
89 Commits
v0.7.0
...
14ff0c0e16
| Author | SHA1 | Date | |
|---|---|---|---|
| 14ff0c0e16 | |||
|
|
d939f5d649
|
||
|
|
69fffb29d8
|
||
|
|
91d3b977e9
|
||
| 7a5fd46835 | |||
|
|
9c4c5c2553
|
||
|
|
8f819d12c0
|
||
|
|
b810e27480
|
||
|
|
1949f1876f
|
||
|
|
2ba0116ca6
|
||
|
|
2c2ddabdff
|
||
|
|
dfcdbec0dd
|
||
|
|
3b67a8791c
|
||
|
|
d5ab532947
|
||
|
|
50c63d5c38
|
||
|
|
64d09cfb7f
|
||
|
|
def44618ef
|
||
|
|
9e5aeaf572
|
||
|
|
86f85a90f4
|
||
| d8a35ac3fd | |||
|
|
5a5f62e98a
|
||
|
|
074f9afcbb
|
||
|
|
725fd2e5ea
|
||
|
|
8349ca5e12
|
||
|
|
46d59e3371
|
||
|
|
e8e6ee0bc4
|
||
|
|
a91ee2bd0a
|
||
|
|
fcb6923c92
|
||
|
|
0f3b9f176e
|
||
| 822ae2f945 | |||
|
|
96c669ab4e
|
||
|
|
558100c35e
|
||
|
|
6739b38f4c
|
||
| 7e1272c936 | |||
|
|
ecdeb4c122
|
||
|
|
8614e2f12b
|
||
|
|
a038a857d9
|
||
|
|
eee81d0cf1
|
||
|
|
b7fa4b012a
|
||
|
|
10bcd5c32b
|
||
|
|
f79d5d4724
|
||
|
|
866ffbe615
|
||
|
|
3c1fe3396d
|
||
|
|
e4242333d9
|
||
|
|
138f13c1a0
|
||
|
|
ad5e515200
|
||
|
|
1ea8b22a59
|
||
|
|
f49aff262c
|
||
| 852e2fea1e | |||
|
|
353b55fe1a
|
||
|
|
ba0cbba96b
|
||
|
|
5f921f1b53
|
||
|
|
a2d27bf575
|
||
|
|
fcf9a065e1
|
||
|
|
ec9bcacd46
|
||
|
|
645abac810 | ||
|
|
e11be727a1 | ||
|
|
12b24337e7 | ||
|
|
b0bfc290c4 | ||
|
|
4c6c81171b | ||
|
|
4d88a40109
|
||
|
|
d9b39b36fb
|
||
|
|
06aed8c33d
|
||
| 0a778e92d8 | |||
|
|
e5a5633e44
|
||
|
|
a68825493f
|
||
|
|
e1e83386a8
|
||
|
|
3adc1917f6
|
||
|
|
8a570ce724
|
||
|
|
c78df9e5f1
|
||
|
5c2df3df07
|
|||
| 83e3e2ecd8 | |||
| b32e2fcb7b | |||
| 96a4db5bae | |||
| c7925f132e | |||
| e4406bf6ff | |||
| ee7769c8c7 | |||
| fdf3218f88 | |||
| 652ed5f7e3 | |||
|
|
e4ed797920
|
||
|
|
93740f17ef
|
||
|
|
affb058671
|
||
|
716d4b944a
|
|||
|
42af148168
|
|||
|
|
dabd892a25
|
||
|
|
eeabbdb7df
|
||
|
ee42d68471
|
|||
|
7acc3b2106
|
|||
|
20c014607c
|
@@ -17,9 +17,11 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
- name: rspec
|
||||
image: guildeducation/rails:2.7.2-14.20.0
|
||||
image: gitea.kosmos.org/kosmos/akkounts-ci:0.1.0
|
||||
environment:
|
||||
RAILS_ENV: test
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
commands:
|
||||
- bundle config unset deployment
|
||||
- bundle config set cache_all 'true'
|
||||
@@ -42,6 +44,10 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
|
||||
services:
|
||||
- name: redis
|
||||
image: redis
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
host:
|
||||
|
||||
@@ -22,15 +22,20 @@ WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
||||
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
|
||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||
|
||||
DRONECI_PUBLIC_URL='https://drone.kosmos.org'
|
||||
|
||||
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
|
||||
MASTODON_PUBLIC_URL='https://kosmos.social'
|
||||
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
||||
RS_STORAGE_URL='https://storage.kosmos.org'
|
||||
RS_REDIS_URL='redis://localhost:6379/2'
|
||||
|
||||
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
|
||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||
|
||||
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||
BTCPAY_STORE_ID=''
|
||||
BTCPAY_AUTH_TOKEN=''
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
PRIMARY_DOMAIN=kosmos.org
|
||||
|
||||
REDIS_URL='redis://localhost:6379/0'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
BTCPAY_STORE_ID='123456'
|
||||
|
||||
DISCOURSE_PUBLIC_URL='http://discourse.example.com'
|
||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||
|
||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3026'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
||||
|
||||
RS_STORAGE_URL='https://storage.kosmos.org'
|
||||
RS_REDIS_URL='redis://localhost:6379/1'
|
||||
|
||||
WEBHOOKS_ALLOWED_IPS='10.1.1.23'
|
||||
|
||||
@@ -7,6 +7,7 @@ version-resolver:
|
||||
minor:
|
||||
labels:
|
||||
- 'release/minor'
|
||||
- 'feature'
|
||||
patch:
|
||||
labels:
|
||||
- 'release/patch'
|
||||
|
||||
2
.gitignore
vendored
@@ -23,6 +23,7 @@
|
||||
!/tmp/pids/
|
||||
!/tmp/pids/.keep
|
||||
|
||||
/storage
|
||||
|
||||
/public/assets
|
||||
.byebug_history
|
||||
@@ -39,6 +40,7 @@ yarn-debug.log*
|
||||
|
||||
# Ignore local dotenv config file
|
||||
.env
|
||||
.env.development
|
||||
|
||||
# Ignore redis dumps from sidekiq
|
||||
dump.rdb
|
||||
|
||||
@@ -4,14 +4,14 @@ FROM ruby:2.7.6
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||
ldap-utils tini
|
||||
ldap-utils tini libvips
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
WORKDIR /akkounts
|
||||
COPY Gemfile /akkounts/Gemfile
|
||||
COPY Gemfile.lock /akkounts/Gemfile.lock
|
||||
COPY package.json /akkounts/package.json
|
||||
|
||||
COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
|
||||
|
||||
RUN bundle install
|
||||
RUN gem install foreman
|
||||
RUN npm install -g yarn
|
||||
|
||||
2
Gemfile
@@ -37,6 +37,7 @@ gem 'devise_ldap_authenticatable'
|
||||
gem 'net-ldap'
|
||||
|
||||
# Utilities
|
||||
gem "image_processing", "~> 1.12.2"
|
||||
gem "rqrcode", "~> 2.0"
|
||||
gem 'rails-settings-cached', '~> 2.8.3'
|
||||
gem 'pagy', '~> 6.0', '>= 6.0.2'
|
||||
@@ -64,6 +65,7 @@ group :development, :test do
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3', '~> 1.4'
|
||||
gem 'rspec-rails'
|
||||
gem 'rails-controller-testing'
|
||||
gem "byebug", "~> 11.1"
|
||||
end
|
||||
|
||||
|
||||
16
Gemfile.lock
@@ -182,6 +182,9 @@ GEM
|
||||
hashdiff (1.0.1)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
importmap-rails (1.1.6)
|
||||
actionpack (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
@@ -220,6 +223,7 @@ GEM
|
||||
marcel (1.0.2)
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.18.0)
|
||||
multipart-post (2.3.0)
|
||||
@@ -234,6 +238,8 @@ GEM
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.2-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
@@ -267,6 +273,10 @@ GEM
|
||||
activesupport (= 7.0.5)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.5)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
@@ -331,6 +341,8 @@ GEM
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.1.4)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
@@ -373,6 +385,7 @@ GEM
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.6.3-arm64-darwin)
|
||||
sqlite3 (1.6.3-x86_64-linux)
|
||||
stimulus-rails (1.2.1)
|
||||
railties (>= 6.0.0)
|
||||
@@ -410,6 +423,7 @@ GEM
|
||||
zeitwerk (2.6.8)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
@@ -427,6 +441,7 @@ DEPENDENCIES
|
||||
flipper
|
||||
flipper-active_record
|
||||
flipper-ui
|
||||
image_processing (~> 1.12.2)
|
||||
importmap-rails
|
||||
jbuilder (~> 2.7)
|
||||
letter_opener
|
||||
@@ -440,6 +455,7 @@ DEPENDENCIES
|
||||
pg (~> 1.2.3)
|
||||
puma (~> 4.1)
|
||||
rails (~> 7.0.2)
|
||||
rails-controller-testing
|
||||
rails-settings-cached (~> 2.8.3)
|
||||
rqrcode (~> 2.0)
|
||||
rspec-rails
|
||||
|
||||
12
README.md
@@ -14,7 +14,6 @@ so:
|
||||
|
||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||
Docker Desktop)
|
||||
2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml`
|
||||
3. Run `docker compose up` and wait until 389ds announces its successful start
|
||||
in the log output
|
||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||
@@ -53,12 +52,14 @@ Running all specs:
|
||||
|
||||
### Docker (Compose)
|
||||
|
||||
There is a working Docker Compose config file, which allows you to spin up both
|
||||
There is a working Docker Compose config file, which define a number of services including
|
||||
an app server for Rails as well as a local 389ds (LDAP) server.
|
||||
|
||||
By default, `docker-compose up` will only start the LDAP server, listening on
|
||||
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
|
||||
you want to use them.
|
||||
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`,
|
||||
listening on port 389 on your machine.
|
||||
|
||||
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at
|
||||
the end of the docker compose command. eg. `docker compose up ldap redis`
|
||||
|
||||
#### LDAP server
|
||||
|
||||
@@ -106,6 +107,7 @@ command:
|
||||
* [Tailwind CSS](https://tailwindcss.com/)
|
||||
* [Sass](https://sass-lang.com/documentation)
|
||||
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
|
||||
* [Tailwind Stimulus Components](https://github.com/excid3/tailwindcss-stimulus-components)
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
@import "components/animations";
|
||||
@import "components/base";
|
||||
@import "components/buttons";
|
||||
@import "components/dashboard_services";
|
||||
|
||||
16
app/assets/stylesheets/components/animations.css
Normal file
@@ -0,0 +1,16 @@
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation-name: scaleIn;
|
||||
animation-duration: 0.15s;
|
||||
animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1);
|
||||
}
|
||||
@@ -14,12 +14,12 @@
|
||||
@apply py-1 px-2 text-sm;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
@apply border-2 border-gray-100 hover:bg-gray-100;
|
||||
.btn-icon {
|
||||
@apply py-2 px-3;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@apply px-3;
|
||||
.btn-outline {
|
||||
@apply py-2 border-2 border-gray-100 hover:bg-gray-100;
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
|
||||
15
app/components/app_info_component.html.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="flex">
|
||||
<div class="<%= @icon_container_class %>">
|
||||
<%= image_tag(@icon_path, class: 'h-full w-full') %>
|
||||
</div>
|
||||
<div class="flex-1 px-4">
|
||||
<h4 class="sm:pt-2 mb-2 text-lg font-bold"><%= @name %></h4>
|
||||
<p class="leading-snug"><%= @description %></p>
|
||||
<p class="leading-snug flex flex-wrap gap-3">
|
||||
<% @links.each do |link| %>
|
||||
<a href="<%= link[1] %>" target="_blank"
|
||||
class="flex-0 btn-sm btn-gray"><%= link[0] %></a>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
19
app/components/app_info_component.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AppInfoComponent < ViewComponent::Base
|
||||
def initialize(name:, description:, icon_path: , icon_fill_box: false, links: [])
|
||||
@name = name
|
||||
@description = description
|
||||
@icon_path = icon_path
|
||||
@icon_container_class = icon_container_class(icon_fill_box)
|
||||
@links = links
|
||||
end
|
||||
|
||||
def icon_container_class(icon_fill_box)
|
||||
str = "flex-0 h-16 w-16 sm:h-28 sm:w-28 bg-white rounded-3xl overflow-hidden"
|
||||
unless icon_fill_box
|
||||
str += " p-2 border border-gray-200"
|
||||
end
|
||||
str
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,6 @@
|
||||
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %>
|
||||
<%= tag.public_send(@tag, class: "mb-6 last:mb-0", data: {
|
||||
:'field-name' => @field_name
|
||||
}) do %>
|
||||
<% if @positioning == :vertical %>
|
||||
<label class="block">
|
||||
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
|
||||
@@ -9,7 +11,21 @@
|
||||
<%= @descripton %>
|
||||
</p>
|
||||
<% end %>
|
||||
<%= content %>
|
||||
|
||||
<%= tag.p class: "flex gap-x-1", data: {
|
||||
controller: @resettable ? "settings--resettable-field" : nil,
|
||||
} do %>
|
||||
<%= content %>
|
||||
<% if @resettable %>
|
||||
<button type="button"
|
||||
class="relative grow-0 shrink-0 btn-md btn-outline text-red-700"
|
||||
title="Reset to default value"
|
||||
data-settings--resettable-field-target="resetButton"
|
||||
data-action="settings--resettable-field#resetField">
|
||||
Reset
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</label>
|
||||
<% elsif @positioning == :horizontal %>
|
||||
<label class="block flex items-center justify-between">
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
module FormElements
|
||||
class FieldsetComponent < ViewComponent::Base
|
||||
def initialize(tag: "li", positioning: :vertical, title:, description: nil)
|
||||
def initialize(tag: "li", positioning: :vertical,
|
||||
title:, description: nil,
|
||||
field_name: nil, resettable: false)
|
||||
@tag = tag
|
||||
@positioning = positioning
|
||||
@title = title
|
||||
@descripton = description
|
||||
@field_name = field_name
|
||||
@resettable = resettable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<%= render FormElements::FieldsetComponent.new(
|
||||
title: @title,
|
||||
description: @description,
|
||||
field_name: "setting_#{@key.to_s}",
|
||||
resettable: @resettable
|
||||
) do %>
|
||||
<%= method("#{@type}_field").call :setting, @key,
|
||||
value: Setting.public_send(@key),
|
||||
data: {
|
||||
:'default-value' => Setting.get_field(@key)[:default]
|
||||
},
|
||||
class: "w-full" %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module FormElements
|
||||
class FieldsetResettableSettingComponent < ViewComponent::Base
|
||||
def initialize(tag: "li", key:, type: :text, title:, description: nil)
|
||||
@tag = tag
|
||||
@positioning = :vertical
|
||||
@title = title
|
||||
@description = description
|
||||
@key = key.to_sym
|
||||
@type = type
|
||||
@resettable = is_resettable?(@key)
|
||||
end
|
||||
|
||||
def is_resettable?(key)
|
||||
default_value = Setting.get_field(key)[:default]
|
||||
default_value.present? && (default_value != Setting.send(key))
|
||||
end
|
||||
end
|
||||
end
|
||||
28
app/components/modal_component.html.erb
Normal file
@@ -0,0 +1,28 @@
|
||||
<div tabindex="-1" class="relative z-10">
|
||||
<!-- Modal Background -->
|
||||
<div class="hidden fixed inset-0 bg-black bg-opacity-80 overflow-y-auto flex items-center justify-center"
|
||||
data-modal-target="background"
|
||||
data-action="click->modal#closeBackground"
|
||||
data-transition-enter="transition-all ease-in-out duration-100"
|
||||
data-transition-enter-from="bg-opacity-0"
|
||||
data-transition-enter-to="bg-opacity-80"
|
||||
data-transition-leave="transition-all ease-in-out duration-100"
|
||||
data-transition-leave-from="bg-opacity-80"
|
||||
data-transition-leave-to="bg-opacity-0">
|
||||
|
||||
<!-- Modal Container -->
|
||||
<div data-modal-target="container"
|
||||
class="max-h-screen w-auto max-w-lg relative
|
||||
hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center">
|
||||
<!-- Modal Card -->
|
||||
<div class="m-1 bg-white rounded shadow">
|
||||
<div class="p-8">
|
||||
<%= content %>
|
||||
<div class="flex justify-end items-center flex-wrap mt-6">
|
||||
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
2
app/components/modal_component.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class ModalComponent < ViewComponent::Base
|
||||
end
|
||||
6
app/components/qr_code_modal_component.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<%= render ModalComponent.new do %>
|
||||
<% if @descripton.present? %>
|
||||
<p class="mb-6"><%= @description %></p>
|
||||
<% end %>
|
||||
<p><%= raw @qr_code_svg %></p>
|
||||
<% end %>
|
||||
24
app/components/qr_code_modal_component.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require "rqrcode"
|
||||
|
||||
class QrCodeModalComponent < ViewComponent::Base
|
||||
def initialize(qr_content:, description: nil)
|
||||
@description = description
|
||||
@qr_code_svg = qr_code_svg(qr_content)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def qr_code_svg(content)
|
||||
qr_code = RQRCode::QRCode.new(content)
|
||||
qr_code.as_svg(
|
||||
color: "000",
|
||||
shape_rendering: "crispEdges",
|
||||
module_size: 6,
|
||||
standalone: true,
|
||||
use_path: true,
|
||||
svg_attributes: {
|
||||
class: 'inline-block'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,8 @@
|
||||
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
|
||||
<% if @icon.present? %>
|
||||
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
|
||||
<% elsif @text_icon.present? %>
|
||||
<span class="mr-3"><%= @text_icon %></span>
|
||||
<% end %>
|
||||
<span class="truncate"><%= @name %></span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SidenavLinkComponent < ViewComponent::Base
|
||||
def initialize(name:, level: 1, path:, icon:, active: false, disabled: false)
|
||||
def initialize(name:, level: 1, path:, icon: nil, text_icon: nil,
|
||||
active: false, disabled: false)
|
||||
@name = name
|
||||
@level = level
|
||||
@path = path
|
||||
@icon = icon
|
||||
@text_icon = text_icon
|
||||
@active = active
|
||||
@disabled = disabled
|
||||
@link_class = class_names_link(path)
|
||||
|
||||
@@ -3,7 +3,7 @@ class Admin::Settings::ServicesController < Admin::SettingsController
|
||||
@service = params[:s]
|
||||
|
||||
if @service.blank?
|
||||
redirect_to admin_settings_services_path(params: { s: "discourse" })
|
||||
redirect_to admin_settings_services_path(params: { s: "btcpay" })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class Admin::UsersController < Admin::BaseController
|
||||
end
|
||||
|
||||
@services_enabled = @user.services_enabled
|
||||
|
||||
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn, ou: @user.ou)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
29
app/controllers/api/btcpay_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
class Api::BtcpayController < Api::BaseController
|
||||
before_action :require_feature_enabled
|
||||
|
||||
def onchain_btc_balance
|
||||
balance = BtcpayManager::FetchOnchainWalletBalance.call
|
||||
render json: balance
|
||||
rescue => error
|
||||
Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}"
|
||||
render json: { error: 'Failed to fetch wallet balance' },
|
||||
status: 500
|
||||
end
|
||||
|
||||
def lightning_btc_balance
|
||||
balance = BtcpayManager::FetchLightningWalletBalance.call
|
||||
render json: balance
|
||||
rescue => error
|
||||
Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}"
|
||||
render json: { error: 'Failed to fetch wallet balance' },
|
||||
status: 500
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_feature_enabled
|
||||
unless Setting.btcpay_publish_wallet_balances
|
||||
http_status :not_found and return
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Api::KreditsController < Api::BaseController
|
||||
|
||||
def onchain_btc_balance
|
||||
btcpay = BtcPay.new
|
||||
balance = btcpay.onchain_wallet_balance
|
||||
render json: balance
|
||||
rescue => error
|
||||
Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}"
|
||||
render json: { error: 'Failed to fetch wallet balance' },
|
||||
status: 500
|
||||
end
|
||||
|
||||
end
|
||||
@@ -37,4 +37,8 @@ class ApplicationController < ActionController::Base
|
||||
format.any { head status }
|
||||
end
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(user)
|
||||
session[:user_return_to] || root_path
|
||||
end
|
||||
end
|
||||
|
||||
146
app/controllers/rs/oauth_controller.rb
Normal file
@@ -0,0 +1,146 @@
|
||||
class Rs::OauthController < ApplicationController
|
||||
before_action :require_signed_in_with_username, only: :new
|
||||
before_action :authenticate_user!, only: :create
|
||||
|
||||
def new
|
||||
username, org = params[:useraddress].split("@")
|
||||
@user = User.where(cn: username.downcase, ou: org).first
|
||||
@scopes = parse_scopes params[:scope]
|
||||
@redirect_uri = params[:redirect_uri]
|
||||
@client_id = params[:client_id]
|
||||
@state = params[:state]
|
||||
@root_access_requested = (@scopes & [":r",":rw"]).any?
|
||||
|
||||
@denial_url = url_with_state("#{@redirect_uri}#error=access_denied", @state)
|
||||
|
||||
@expire_at_dates = [["Never", nil],
|
||||
["In 1 month", 1.month.from_now],
|
||||
["In 1 day", 1.day.from_now]]
|
||||
|
||||
http_status :bad_request and return unless @redirect_uri.present?
|
||||
|
||||
unless current_user == @user
|
||||
sign_out :user
|
||||
|
||||
redirect_to new_rs_oauth_url(@user.address,
|
||||
scope: params[:scope],
|
||||
redirect_uri: params[:redirect_uri],
|
||||
client_id: params[:client_id],
|
||||
state: params[:state])
|
||||
return
|
||||
end
|
||||
|
||||
unless @client_id.present?
|
||||
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_request", @state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
if @scopes.empty?
|
||||
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_scope", @state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
unless hostname_of(@client_id) == hostname_of(@redirect_uri)
|
||||
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_client", @state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
@client_id.gsub!(/http(s)?:\/\//, "")
|
||||
|
||||
if auth = current_user.remote_storage_authorizations.valid.where(permissions: @scopes, client_id: @client_id).first
|
||||
redirect_to(url_with_state("#{@redirect_uri}#access_token=#{auth.token}", @state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
unless current_user.id.to_s == params[:user_id]
|
||||
Rails.logger.info("NO MATCH: #{params[:user_id]}, #{current_user.id}")
|
||||
http_status :forbidden and return
|
||||
end
|
||||
|
||||
permissions = parse_scopes params[:scope]
|
||||
redirect_uri = params[:redirect_uri].presence
|
||||
client_id = params[:client_id].presence
|
||||
state = params[:state].presence
|
||||
expire_at = params[:expire_at].presence
|
||||
|
||||
http_status :bad_request and return unless redirect_uri.present?
|
||||
|
||||
if permissions.empty?
|
||||
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
unless client_id.present?
|
||||
redirect_to(url_with_state("#{redirect_uri}#error=invalid_request", state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
unless hostname_of(client_id) == hostname_of(redirect_uri)
|
||||
redirect_to(url_with_state("#{redirect_uri}#error=invalid_client", state),
|
||||
allow_other_host: true) and return
|
||||
end
|
||||
|
||||
client_id.gsub!(/http(s)?:\/\//, "")
|
||||
|
||||
auth = current_user.remote_storage_authorizations.create!(
|
||||
permissions: permissions,
|
||||
client_id: client_id,
|
||||
redirect_uri: redirect_uri,
|
||||
app_name: client_id, #TODO use user-defined name
|
||||
expire_at: expire_at
|
||||
)
|
||||
|
||||
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state),
|
||||
allow_other_host: true
|
||||
end
|
||||
|
||||
# GET /rs/oauth/token/:id/launch_app
|
||||
def launch_app
|
||||
auth = current_user.remote_storage_authorizations.find(params[:id])
|
||||
|
||||
redirect_to app_auth_url(auth), allow_other_host: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_signed_in_with_username
|
||||
unless user_signed_in?
|
||||
username, org = params[:useraddress].split("@")
|
||||
session[:user_return_to] = request.url
|
||||
redirect_to new_user_session_path(cn: username, ou: org)
|
||||
end
|
||||
end
|
||||
|
||||
def app_auth_url(auth)
|
||||
url = "#{auth.url}#remotestorage=#{current_user.address}"
|
||||
url += "&access_token=#{auth.token}"
|
||||
url
|
||||
end
|
||||
|
||||
def hostname_of(uri)
|
||||
uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0]
|
||||
end
|
||||
|
||||
def parse_scopes(scope_string)
|
||||
return [] if scope_string.blank?
|
||||
|
||||
scopes = scope_string.
|
||||
gsub(/\[|\]/, "").
|
||||
gsub(/\,/, " ").
|
||||
gsub(/\/:/, ":").
|
||||
split(/\s/).map(&:strip).
|
||||
reject(&:empty?)
|
||||
|
||||
scopes = [":r"] if scopes.include?("*:r")
|
||||
scopes = [":rw"] if scopes.include?("*:rw")
|
||||
|
||||
scopes
|
||||
end
|
||||
|
||||
def url_with_state(url, state)
|
||||
state ? "#{url}&state=#{CGI.escape(state)}" : url
|
||||
end
|
||||
|
||||
end
|
||||
9
app/controllers/services/base_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Services::BaseController < ApplicationController
|
||||
before_action :set_current_section
|
||||
|
||||
private
|
||||
|
||||
def set_current_section
|
||||
@current_section = :services
|
||||
end
|
||||
end
|
||||
14
app/controllers/services/chat_controller.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class Services::ChatController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_service_available
|
||||
|
||||
def show
|
||||
@service_enabled = current_user.services_enabled.include?(:xmpp)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_service_available
|
||||
http_status :not_found unless Setting.ejabberd_enabled?
|
||||
end
|
||||
end
|
||||
@@ -8,8 +8,7 @@ class Services::LightningController < ApplicationController
|
||||
before_action :fetch_balance
|
||||
|
||||
def index
|
||||
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
initialize_lndhub_qr_code
|
||||
@wallet_setup_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
end
|
||||
|
||||
def transactions
|
||||
@@ -56,20 +55,6 @@ class Services::LightningController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def initialize_lndhub_qr_code
|
||||
qr_code = RQRCode::QRCode.new(@wallet_url)
|
||||
@lndhub_qr_svg = qr_code.as_svg(
|
||||
color: "000",
|
||||
shape_rendering: "crispEdges",
|
||||
module_size: 6,
|
||||
standalone: true,
|
||||
use_path: true,
|
||||
svg_attributes: {
|
||||
class: 'inline-block'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def authenticate_with_lndhub(options={})
|
||||
if session[:ln_auth_token].present? && !options[:force_reauth]
|
||||
@ln_auth_token = session[:ln_auth_token]
|
||||
|
||||
14
app/controllers/services/mastodon_controller.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class Services::MastodonController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_service_available
|
||||
|
||||
def show
|
||||
@service_enabled = current_user.services_enabled.include?(:mastodon)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_service_available
|
||||
http_status :not_found unless Setting.mastodon_enabled?
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,7 @@
|
||||
class Services::RemotestorageController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :require_service_enabled
|
||||
class Services::RemotestorageController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_feature_enabled
|
||||
before_action :set_current_section
|
||||
before_action :require_service_available
|
||||
|
||||
def dashboard
|
||||
# unless current_user.services_enabled.include?(:remotestorage)
|
||||
@@ -18,13 +17,7 @@ class Services::RemotestorageController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def require_service_enabled
|
||||
unless Setting.remotestorage_enabled?
|
||||
http_status :not_found
|
||||
end
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@current_section = :services
|
||||
def require_service_available
|
||||
http_status :not_found unless Setting.remotestorage_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,10 +19,15 @@ class SettingsController < ApplicationController
|
||||
def update
|
||||
@user.preferences.merge!(user_params[:preferences] || {})
|
||||
@user.display_name = user_params[:display_name]
|
||||
@user.avatar_new = user_params[:avatar]
|
||||
|
||||
if @user.save
|
||||
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
||||
LdapManager::UpdateDisplayName.call(@user.dn, user_params[:display_name])
|
||||
LdapManager::UpdateDisplayName.call(@user.dn, @user.display_name)
|
||||
end
|
||||
|
||||
if @user.avatar_new.present?
|
||||
LdapManager::UpdateAvatar.call(@user.dn, @user.avatar_new)
|
||||
end
|
||||
|
||||
redirect_to setting_path(@settings_section), flash: {
|
||||
@@ -117,7 +122,7 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:display_name, preferences: [
|
||||
params.require(:user).permit(:display_name, :avatar, preferences: [
|
||||
:lightning_notify_sats_received,
|
||||
:xmpp_exchange_contacts_with_invitees
|
||||
])
|
||||
|
||||
11
app/helpers/oauth_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module OauthHelper
|
||||
|
||||
def scope_name(scope)
|
||||
scope.gsub(/(\:.+)/, '')
|
||||
end
|
||||
|
||||
def scope_permissions(scope)
|
||||
scope.match(/\:r$/) ? "r" : "rw"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Application } from "@hotwired/stimulus"
|
||||
import { Modal, Tabs } from "tailwindcss-stimulus-components"
|
||||
|
||||
const application = Application.start()
|
||||
|
||||
application.register('modal', Modal)
|
||||
application.register('tabs', Tabs)
|
||||
|
||||
// Configure Stimulus development experience
|
||||
application.debug = false
|
||||
window.Stimulus = application
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "resetButton" ]
|
||||
|
||||
resetField () {
|
||||
const inputEl = this.element.querySelector('input')
|
||||
inputEl.value = inputEl.dataset.defaultValue
|
||||
}
|
||||
}
|
||||
10
app/jobs/remote_storage_expire_authorization_job.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class RemoteStorageExpireAuthorizationJob < ApplicationJob
|
||||
queue_as :remotestorage
|
||||
|
||||
def perform(rs_auth_id)
|
||||
rs_auth = RemoteStorageAuthorization.find rs_auth_id
|
||||
return unless rs_auth.expire_at.nil? || rs_auth.expire_at <= DateTime.now
|
||||
|
||||
rs_auth.destroy!
|
||||
end
|
||||
end
|
||||
63
app/models/remote_storage_authorization.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
class RemoteStorageAuthorization < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
serialize :permissions
|
||||
|
||||
validates_presence_of :permissions
|
||||
validates_presence_of :client_id
|
||||
|
||||
scope :valid, -> { where(expire_at: nil).or(where(expire_at: (DateTime.now)..)) }
|
||||
scope :expired, -> { where(expire_at: ..(DateTime.now)) }
|
||||
|
||||
after_initialize do |a|
|
||||
a.permissions = [] if a.permissions == nil
|
||||
end
|
||||
|
||||
before_create :generate_token
|
||||
before_create :store_token_in_redis
|
||||
after_create :schedule_token_expiry
|
||||
before_destroy :delete_token_from_redis
|
||||
after_destroy :remove_token_expiry_job
|
||||
|
||||
def url
|
||||
if self.redirect_uri
|
||||
uri = URI.parse self.redirect_uri
|
||||
"#{uri.scheme}://#{client_id}"
|
||||
else
|
||||
"http://#{client_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def delete_token_from_redis
|
||||
key = "rs:authorizations:#{user.address}:#{token}"
|
||||
redis.srem? key, redis.smembers(key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redis
|
||||
@redis ||= Redis.new(url: Setting.rs_redis_url)
|
||||
end
|
||||
|
||||
def generate_token(length=16)
|
||||
self.token = SecureRandom.hex(length) if self.token.blank?
|
||||
end
|
||||
|
||||
def store_token_in_redis
|
||||
redis.sadd "rs:authorizations:#{user.address}:#{token}", permissions
|
||||
end
|
||||
|
||||
def schedule_token_expiry
|
||||
return unless expire_at.present?
|
||||
RemoteStorageExpireAuthorizationJob.set(wait_until: expire_at)
|
||||
.perform_later(id)
|
||||
end
|
||||
|
||||
def remove_token_expiry_job
|
||||
queue = Sidekiq::Queue.new(RemoteStorageExpireAuthorizationJob.queue_name)
|
||||
queue.each do |job|
|
||||
next unless job.display_class == "RemoteStorageExpireAuthorizationJob"
|
||||
job.delete if job.display_args == [id]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,7 +12,7 @@ class Setting < RailsSettings::Base
|
||||
# Internal services
|
||||
#
|
||||
|
||||
field :redis_url, type: :string, readonly: true,
|
||||
field :redis_url, type: :string,
|
||||
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
||||
|
||||
#
|
||||
@@ -29,38 +29,67 @@ class Setting < RailsSettings::Base
|
||||
|
||||
field :xmpp_default_rooms, type: :array, default: []
|
||||
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
|
||||
field :xmpp_notifications_from_address, type: :string, default: primary_domain
|
||||
|
||||
#
|
||||
# Sentry
|
||||
#
|
||||
|
||||
field :sentry_enabled, type: :boolean, readonly: true,
|
||||
default: (ENV["SENTRY_DSN"].present?.to_s || false)
|
||||
default: ENV["SENTRY_DSN"].present?
|
||||
|
||||
#
|
||||
# BTCPay Server
|
||||
#
|
||||
|
||||
field :btcpay_api_url, type: :string,
|
||||
default: ENV["BTCPAY_API_URL"].presence
|
||||
|
||||
field :btcpay_enabled, type: :boolean,
|
||||
default: ENV["BTCPAY_API_URL"].present?
|
||||
|
||||
field :btcpay_store_id, type: :string,
|
||||
default: ENV["BTCPAY_STORE_ID"].presence
|
||||
|
||||
field :btcpay_auth_token, type: :string,
|
||||
default: ENV["BTCPAY_AUTH_TOKEN"].presence
|
||||
|
||||
field :btcpay_publish_wallet_balances, type: :boolean, default: true
|
||||
|
||||
#
|
||||
# Discourse
|
||||
#
|
||||
|
||||
field :discourse_public_url, type: :string, readonly: true,
|
||||
field :discourse_public_url, type: :string,
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
||||
|
||||
field :discourse_enabled, type: :boolean,
|
||||
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].present?
|
||||
|
||||
field :discourse_connect_secret, type: :string, readonly: true,
|
||||
field :discourse_connect_secret, type: :string,
|
||||
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
||||
|
||||
#
|
||||
# Drone CI
|
||||
#
|
||||
|
||||
field :droneci_public_url, type: :string,
|
||||
default: ENV["DRONECI_PUBLIC_URL"].presence
|
||||
|
||||
field :droneci_enabled, type: :boolean,
|
||||
default: ENV["DRONECI_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# ejabberd
|
||||
#
|
||||
|
||||
field :ejabberd_enabled, type: :boolean,
|
||||
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
|
||||
default: ENV["EJABBERD_API_URL"].present?
|
||||
|
||||
field :ejabberd_api_url, type: :string, readonly: true,
|
||||
field :ejabberd_api_url, type: :string,
|
||||
default: ENV["EJABBERD_API_URL"].presence
|
||||
|
||||
field :ejabberd_admin_url, type: :string, readonly: true,
|
||||
field :ejabberd_admin_url, type: :string,
|
||||
default: ENV["EJABBERD_ADMIN_URL"].presence
|
||||
|
||||
field :ejabberd_buddy_roster, type: :string,
|
||||
@@ -70,50 +99,56 @@ class Setting < RailsSettings::Base
|
||||
# Gitea
|
||||
#
|
||||
|
||||
field :gitea_public_url, type: :string, readonly: true,
|
||||
field :gitea_public_url, type: :string,
|
||||
default: ENV["GITEA_PUBLIC_URL"].presence
|
||||
|
||||
field :gitea_enabled, type: :boolean,
|
||||
default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["GITEA_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# Lightning Network
|
||||
#
|
||||
|
||||
field :lndhub_api_url, type: :string, readonly: true,
|
||||
field :lndhub_api_url, type: :string,
|
||||
default: ENV["LNDHUB_API_URL"].presence
|
||||
|
||||
field :lndhub_enabled, type: :boolean,
|
||||
default: (ENV["LNDHUB_API_URL"].present?.to_s || false)
|
||||
default: ENV["LNDHUB_API_URL"].present?
|
||||
|
||||
field :lndhub_admin_token, type: :string,
|
||||
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
|
||||
|
||||
field :lndhub_admin_enabled, type: :boolean,
|
||||
default: (ENV["LNDHUB_ADMIN_UI"] || false)
|
||||
default: ENV["LNDHUB_ADMIN_UI"] || false
|
||||
|
||||
field :lndhub_public_key, type: :string, readonly: true,
|
||||
field :lndhub_public_key, type: :string,
|
||||
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
|
||||
|
||||
field :lndhub_keysend_enabled, type: :boolean,
|
||||
default: -> { self.lndhub_public_key.present?.to_s || false }
|
||||
default: -> { self.lndhub_public_key.present? }
|
||||
|
||||
#
|
||||
# Mastodon
|
||||
#
|
||||
|
||||
field :mastodon_public_url, type: :string, readonly: true,
|
||||
field :mastodon_public_url, type: :string,
|
||||
default: ENV["MASTODON_PUBLIC_URL"].presence
|
||||
|
||||
field :mastodon_enabled, type: :boolean,
|
||||
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["MASTODON_PUBLIC_URL"].present?
|
||||
|
||||
field :mastodon_address_domain, type: :string,
|
||||
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
|
||||
|
||||
#
|
||||
# MediaWiki
|
||||
#
|
||||
|
||||
field :mediawiki_public_url, type: :string, readonly: true,
|
||||
field :mediawiki_public_url, type: :string,
|
||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
|
||||
|
||||
field :mediawiki_enabled, type: :boolean,
|
||||
default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# Nostr
|
||||
@@ -126,8 +161,11 @@ class Setting < RailsSettings::Base
|
||||
#
|
||||
|
||||
field :remotestorage_enabled, type: :boolean,
|
||||
default: (ENV["RS_STORAGE_URL"].present?.to_s || false)
|
||||
default: ENV["RS_STORAGE_URL"].present?
|
||||
|
||||
field :rs_storage_url, type: :string,
|
||||
default: ENV["RS_STORAGE_URL"].presence
|
||||
|
||||
field :rs_redis_url, type: :string,
|
||||
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
|
||||
end
|
||||
|
||||
@@ -2,10 +2,14 @@ class User < ApplicationRecord
|
||||
include EmailValidatable
|
||||
|
||||
attr_accessor :display_name
|
||||
attr_accessor :avatar_new
|
||||
|
||||
serialize :preferences, UserPreferences
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :invitations, dependent: :destroy
|
||||
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
||||
has_one :inviter, through: :invitation, source: :user
|
||||
@@ -18,6 +22,12 @@ class User < ApplicationRecord
|
||||
|
||||
has_many :accounts, through: :lndhub_user
|
||||
|
||||
has_many :remote_storage_authorizations
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates_uniqueness_of :cn, scope: :ou
|
||||
validates_length_of :cn, minimum: 3
|
||||
validates_format_of :cn, with: /\A([a-z0-9\-])*\z/,
|
||||
@@ -38,10 +48,20 @@ class User < ApplicationRecord
|
||||
|
||||
validates_uniqueness_of :nostr_pubkey, allow_blank: true
|
||||
|
||||
validate :acceptable_avatar
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
scope :pending, -> { where(confirmed_at: nil) }
|
||||
scope :all_except, -> (user) { where.not(id: user) }
|
||||
|
||||
#
|
||||
# Encrypted database columns
|
||||
#
|
||||
|
||||
has_encrypted :ln_login, :ln_password
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
@@ -70,6 +90,7 @@ class User < ApplicationRecord
|
||||
# E-Mail update confirmed
|
||||
LdapManager::UpdateEmail.call(self.dn, self.email)
|
||||
else
|
||||
# TODO Make configurable
|
||||
# E-Mail from signup confirmed (i.e. account activation)
|
||||
enable_service %w[ discourse gitea mediawiki xmpp ]
|
||||
|
||||
@@ -107,6 +128,11 @@ class User < ApplicationRecord
|
||||
"#{self.cn}@#{self.ou}"
|
||||
end
|
||||
|
||||
def mastodon_address
|
||||
return nil unless Setting.mastodon_enabled?
|
||||
"#{self.cn}@#{Setting.mastodon_address_domain}"
|
||||
end
|
||||
|
||||
def valid_attribute?(attribute_name)
|
||||
self.valid?
|
||||
self.errors[attribute_name].blank?
|
||||
@@ -132,6 +158,10 @@ class User < ApplicationRecord
|
||||
@display_name ||= ldap_entry[:display_name]
|
||||
end
|
||||
|
||||
def avatar
|
||||
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn, ou: ou)
|
||||
end
|
||||
|
||||
def services_enabled
|
||||
ldap_entry[:service] || []
|
||||
end
|
||||
@@ -160,4 +190,17 @@ class User < ApplicationRecord
|
||||
return @ldap_service if defined?(@ldap_service)
|
||||
@ldap_service = LdapService.new
|
||||
end
|
||||
|
||||
def acceptable_avatar
|
||||
return unless avatar_new.present?
|
||||
|
||||
if avatar_new.size > 1.megabyte
|
||||
errors.add(:avatar, "file size is too large")
|
||||
end
|
||||
|
||||
acceptable_types = ["image/jpeg", "image/png"]
|
||||
unless acceptable_types.include?(avatar_new.content_type)
|
||||
errors.add(:avatar, "must be a JPEG or PNG file")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#
|
||||
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
|
||||
#
|
||||
class BtcPay
|
||||
def initialize
|
||||
@base_url = ENV["BTCPAY_API_URL"]
|
||||
@store_id = Rails.application.credentials.btcpay[:store_id]
|
||||
@auth_token = Rails.application.credentials.btcpay[:auth_token]
|
||||
end
|
||||
|
||||
def onchain_wallet_balance
|
||||
res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet"
|
||||
|
||||
{
|
||||
balance: res["balance"].to_f,
|
||||
unconfirmed_balance: res["unconfirmedBalance"].to_f,
|
||||
confirmed_balance: res["confirmedBalance"].to_f
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get(endpoint)
|
||||
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "token #{@auth_token}"
|
||||
})
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
module BtcpayManager
|
||||
class FetchLightningWalletBalance < BtcpayManagerService
|
||||
def call
|
||||
res = get "stores/#{store_id}/lightning/BTC/balance"
|
||||
|
||||
{
|
||||
balance: res["offchain"]["local"].to_i / 1000 # msats to sats
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/services/btcpay_manager/fetch_onchain_wallet_balance.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module BtcpayManager
|
||||
class FetchOnchainWalletBalance < BtcpayManagerService
|
||||
def call
|
||||
res = get "stores/#{store_id}/payment-methods/onchain/BTC/wallet"
|
||||
|
||||
{
|
||||
balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats
|
||||
unconfirmed_balance: (res["unconfirmedBalance"].to_f * 100000000).to_i,
|
||||
confirmed_balance: (res["confirmedBalance"].to_f * 100000000).to_i
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/services/btcpay_manager_service.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
|
||||
#
|
||||
class BtcpayManagerService < ApplicationService
|
||||
attr_reader :base_url, :store_id, :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = Setting.btcpay_api_url
|
||||
@store_id = Setting.btcpay_store_id
|
||||
@auth_token = Setting.btcpay_auth_token
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get(endpoint)
|
||||
res = Faraday.get("#{base_url}/#{endpoint}", {}, {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "token #{auth_token}"
|
||||
})
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
end
|
||||
17
app/services/ldap_manager/fetch_avatar.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module LdapManager
|
||||
class FetchAvatar < LdapManagerService
|
||||
def initialize(cn:, ou: nil)
|
||||
@cn = cn
|
||||
@ou = ou
|
||||
end
|
||||
|
||||
def call
|
||||
treebase = @ou ? "ou=#{@ou},cn=users,#{suffix}" : ldap_config["base"]
|
||||
attributes = %w{ jpegPhoto }
|
||||
filter = Net::LDAP::Filter.eq("cn", @cn)
|
||||
|
||||
entry = ldap_client.search(base: treebase, filter: filter, attributes: attributes).first
|
||||
entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/services/ldap_manager/update_avatar.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
require "image_processing/vips"
|
||||
|
||||
module LdapManager
|
||||
class UpdateAvatar < LdapManagerService
|
||||
def initialize(dn, file)
|
||||
@dn = dn
|
||||
@img_data = process(file)
|
||||
end
|
||||
|
||||
def call
|
||||
replace_attribute @dn, :jpegPhoto, @img_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process(file)
|
||||
processed = ImageProcessing::Vips
|
||||
.resize_to_fill(512, 512)
|
||||
.source(file)
|
||||
.convert("jpeg")
|
||||
.saver(strip: true)
|
||||
.call
|
||||
|
||||
Base64.strict_encode64 processed.read
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,5 @@
|
||||
class LdapManagerService < LdapService
|
||||
def suffix
|
||||
@suffix ||= ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ class LndhubV2 < Lndhub
|
||||
end
|
||||
|
||||
def create_account(payload={})
|
||||
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
||||
post "v2/users", payload, admin_token: Setting.lndhub_admin_token
|
||||
end
|
||||
|
||||
def create_invoice(payload)
|
||||
|
||||
37
app/views/admin/settings/services/_btcpay.html.erb
Normal file
@@ -0,0 +1,37 @@
|
||||
<h3>BTCPay Server</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :btcpay_enabled,
|
||||
enabled: Setting.btcpay_enabled?,
|
||||
title: "Enable BTCPay integration",
|
||||
description: "BTCPay configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.btcpay_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_store_id,
|
||||
title: "Store ID"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_auth_token,
|
||||
type: :password,
|
||||
title: "Auth Token"
|
||||
) %>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h3>REST API</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :btcpay_publish_wallet_balances,
|
||||
enabled: Setting.btcpay_publish_wallet_balances?,
|
||||
title: "Publish wallet balances",
|
||||
description: "Publish the store's on-chain and Lightning wallet balances"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
@@ -8,16 +8,15 @@
|
||||
description: "Discourse configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :discourse_public_url,
|
||||
value: Setting.discourse_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Connect secret") do %>
|
||||
<%= f.password_field :discourse_connect_secret,
|
||||
value: Setting.discourse_connect_secret,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :discourse_public_url,
|
||||
title: "Public URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :discourse_connect_secret,
|
||||
type: :password,
|
||||
title: "Connect secret"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
@@ -31,14 +30,14 @@
|
||||
<input type="text" class="grow" disabled="disabled"
|
||||
value="https://<%= Setting.accounts_domain %>/discourse/connect"
|
||||
data-clipboard-target="source" />
|
||||
<button class="btn-md btn-icon btn-blue shrink-0"
|
||||
<button class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
16
app/views/admin/settings/services/_droneci.html.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
<h3>Drone CI</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :droneci_enabled,
|
||||
enabled: Setting.droneci_enabled?,
|
||||
title: "Enable Drone CI integration",
|
||||
description: "Drone CI configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.droneci_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :droneci_public_url,
|
||||
title: "Public URL"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
@@ -8,16 +8,14 @@
|
||||
description: "ejabberd configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.ejabberd_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
|
||||
<%= f.text_field :ejabberd_api_url,
|
||||
value: Setting.ejabberd_api_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Admin URL") do %>
|
||||
<%= f.text_field :ejabberd_admin_url,
|
||||
value: Setting.ejabberd_admin_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :ejabberd_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :ejabberd_admin_url,
|
||||
title: "Admin URL"
|
||||
) %>
|
||||
</ul>
|
||||
<h3 class="mt-10">User default settings</h3>
|
||||
<ul role="list">
|
||||
@@ -37,12 +35,24 @@
|
||||
title: "Auto-join default rooms",
|
||||
description: "Automatically join above default rooms in chat clients"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetComponent.new(
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :ejabberd_buddy_roster,
|
||||
title: "Contact roster name",
|
||||
description: "Used when exchanging contacts after signup from invitation"
|
||||
) %>
|
||||
</ul>
|
||||
<h3 class="mt-10">Notifications</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetComponent.new(
|
||||
title: "From address",
|
||||
description: "Address (JID) of the account notifications are sent from",
|
||||
resettable: Setting.get_field(:xmpp_notifications_from_address)[:default] != Setting.xmpp_notifications_from_address
|
||||
) do %>
|
||||
<%= f.text_field :ejabberd_buddy_roster,
|
||||
value: Setting.ejabberd_buddy_roster,
|
||||
<%= f.text_field :xmpp_notifications_from_address,
|
||||
value: Setting.xmpp_notifications_from_address,
|
||||
data: {
|
||||
:'default-value' => Setting.get_field(:xmpp_notifications_from_address)[:default]
|
||||
},
|
||||
class: "w-full" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
description: "Gitea configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.gitea_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :gitea_public_url,
|
||||
value: Setting.gitea_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :gitea_public_url,
|
||||
title: "Public URL"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -8,31 +8,36 @@
|
||||
description: "LNDHub configuration present and wallet features enabled"
|
||||
) %>
|
||||
<% if Setting.lndhub_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "API URL") do %>
|
||||
<%= f.text_field :lndhub_api_url,
|
||||
value: Setting.lndhub_api_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_admin_enabled,
|
||||
enabled: Setting.lndhub_admin_enabled?,
|
||||
title: "Enable LNDHub admin panel",
|
||||
description: "LNDHub database configuration present and admin panel enabled"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_keysend_enabled,
|
||||
enabled: Setting.lndhub_keysend_enabled?,
|
||||
title: "Enable keysend payments",
|
||||
description: "Allow users to receive invoice-less payments to their Lightning Address"
|
||||
) %>
|
||||
<% if Setting.lndhub_keysend_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public key", description: "The public key of the Lightning node used by LNDHub") do %>
|
||||
<%= f.text_field :lndhub_public_key,
|
||||
value: Setting.lndhub_public_key,
|
||||
class: "w-full", disabled: true %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_admin_token,
|
||||
type: :password,
|
||||
title: "Admin token",
|
||||
description: "Auth token for creating new lndhub accounts"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_admin_enabled,
|
||||
enabled: Setting.lndhub_admin_enabled?,
|
||||
title: "Enable LNDHub admin panel",
|
||||
description: "LNDHub database configuration present and admin panel enabled"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_keysend_enabled,
|
||||
enabled: Setting.lndhub_keysend_enabled?,
|
||||
title: "Enable keysend payments",
|
||||
description: "Allow users to receive invoice-less payments to their Lightning Address"
|
||||
) %>
|
||||
<% if Setting.lndhub_keysend_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_public_key,
|
||||
title: "Public key",
|
||||
description: "The public key of the Lightning node used by LNDHub"
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
description: "Mastodon configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.mastodon_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :mastodon_public_url,
|
||||
value: Setting.mastodon_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :mastodon_public_url,
|
||||
title: "Public URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :mastodon_address_domain,
|
||||
title: "User address domain"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
description: "MediaWiki configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.mediawiki_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :mediawiki_public_url,
|
||||
value: Setting.mediawiki_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :mediawiki_public_url,
|
||||
title: "Public URL"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<h3>RemoteStorage</h3>
|
||||
<p class="text-red-600 mb-8">Feature currently in development.</p>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
@@ -8,10 +9,13 @@
|
||||
description: "RemoteStorage configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.remotestorage_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Storage URL") do %>
|
||||
<%= f.text_field :rs_storage_url,
|
||||
value: Setting.rs_storage_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :rs_storage_url,
|
||||
title: "Storage Base URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :rs_redis_url,
|
||||
title: "Redis URL"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
</section>
|
||||
|
||||
<section class="sm:flex-1 sm:pt-0">
|
||||
<h3>LDAP<h3>
|
||||
<p>
|
||||
<img src="data:image/jpeg;base64,<%= @avatar %>" class="h-48 w-48" />
|
||||
</p>
|
||||
<!-- <h3>Actions</h3> -->
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -7,73 +7,85 @@
|
||||
services:
|
||||
</p>
|
||||
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-50px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_xmpp.svg)]">
|
||||
<%= link_to "https://wiki.kosmos.org/Services:Chat",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Chat</h3>
|
||||
<p class="text-gray-600">
|
||||
Federated chat rooms and instant messaging
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:95%] bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos community forums and user support/help site
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-20px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_mediawiki.svg)]">
|
||||
<%= link_to "https://wiki.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Wiki</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_lightning.svg)]">
|
||||
<%= link_to services_lightning_index_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Lightning Network</h3>
|
||||
<p class="text-gray-600">
|
||||
Send and receive sats over the Bitcoin Lightning Network
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_gitea.png)]">
|
||||
<%= link_to "https://gitea.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Gitea</h3>
|
||||
<p class="text-gray-600">
|
||||
Code hosting and collaboration for software projects
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-70px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_droneci.svg)]">
|
||||
<%= link_to "https://drone.kosmos.org",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Drone CI</h3>
|
||||
<p class="text-gray-600">
|
||||
Continuous integration for software projects on Gitea
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if Setting.remotestorage_enabled? && Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<% if Setting.ejabberd_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-50px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_xmpp.svg)]">
|
||||
<%= link_to services_chat_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Chat</h3>
|
||||
<p class="text-gray-600">
|
||||
Federated chat rooms and instant messaging
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.mastodon_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:80%] bg-[right_top_-30px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_mastodon.svg)]">
|
||||
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Mastodon</h3>
|
||||
<p class="text-gray-600">
|
||||
Your account on the Open Social Web
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:95%] bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos community forums and user support/help site
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.lndhub_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_lightning.svg)]">
|
||||
<%= link_to services_lightning_index_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Lightning Network</h3>
|
||||
<p class="text-gray-600">
|
||||
Send and receive sats over the Bitcoin Lightning Network
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.gitea_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_gitea.png)]">
|
||||
<%= link_to Setting.gitea_public_url,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Gitea</h3>
|
||||
<p class="text-gray-600">
|
||||
Code hosting and collaboration for software projects
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.droneci_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-70px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_droneci.svg)]">
|
||||
<%= link_to Setting.droneci_public_url,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Drone CI</h3>
|
||||
<p class="text-gray-600">
|
||||
Continuous integration for software projects on Gitea
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled? &&
|
||||
Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
||||
<%= link_to services_storage_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
@@ -84,16 +96,19 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<!-- <div class="border border-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> -->
|
||||
<% if Setting.mediawiki_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-cover bg-[center_top_-20px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_mediawiki.svg)]">
|
||||
<%= link_to Setting.mediawiki_public_url,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Wiki</h3>
|
||||
<p class="text-gray-600">
|
||||
Kosmos documentation and knowledge base
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
<div class="mb-6">
|
||||
<%= f.label :cn, 'User', class: 'block mb-2 font-bold' %>
|
||||
<p class="flex gap-2 items-center">
|
||||
<%= f.text_field :cn, autofocus: true, autocomplete: "username",
|
||||
<%= f.text_field :cn, value: h(params[:cn]),
|
||||
autofocus: params[:cn].blank?, autocomplete: "username",
|
||||
required: true, class: "relative grow", tabindex: "1" %>
|
||||
<span class="relative shrink-0 text-gray-500">@ <%= Setting.primary_domain %></span>
|
||||
</p>
|
||||
@@ -20,7 +21,8 @@
|
||||
<p class="mb-8">
|
||||
<%= f.label :password, class: 'block mb-2 font-bold' %>
|
||||
<%= f.password_field :password, autocomplete: "current-password",
|
||||
required: true, class: "w-full", tabindex: "2" %>
|
||||
autofocus: params[:cn].present?, required: true,
|
||||
class: "w-full", tabindex: "2" %>
|
||||
</p>
|
||||
|
||||
<%= tag.div class: "flex items-center mb-8 gap-x-3", data: {
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-triangle <%= custom_class %>"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 424 B After Width: | Height: | Size: 445 B |
1
app/views/icons/_asterisk.html.erb
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512" fill="currentColor" stroke="currentColor" stroke-width="2" class="<%= custom_class %>"><path d="M475.31 364.144L288 256l187.31-108.144c5.74-3.314 7.706-10.653 4.392-16.392l-4-6.928c-3.314-5.74-10.653-7.706-16.392-4.392L272 228.287V12c0-6.627-5.373-12-12-12h-8c-6.627 0-12 5.373-12 12v216.287L52.69 120.144c-5.74-3.314-13.079-1.347-16.392 4.392l-4 6.928c-3.314 5.74-1.347 13.079 4.392 16.392L224 256 36.69 364.144c-5.74 3.314-7.706 10.653-4.392 16.392l4 6.928c3.314 5.74 10.653 7.706 16.392 4.392L240 283.713V500c0 6.627 5.373 12 12 12h8c6.627 0 12-5.373 12-12V283.713l187.31 108.143c5.74 3.314 13.079 1.347 16.392-4.392l4-6.928c3.314-5.74 1.347-13.079-4.392-16.392z"/></svg>
|
||||
|
After Width: | Height: | Size: 760 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder <%= custom_class %>"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 331 B |
11
app/views/icons/_qr_code.html.erb
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg class="icon-qr-code <%= custom_class %>" fill="currentColor" width="90" height="90" version="1.1" viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="path2" d="m22.014 22.612c0-2.5389 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0587 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
|
||||
<path id="path4" d="m22.014 61.598c0-2.539 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0586 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
|
||||
<path id="path6" d="m50 22.612c0-2.5389 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0587 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
|
||||
<path id="path8" d="m50 61.598c0-2.539 2.0586-4.5976 4.5976-4.5976h9.1937c2.539 0 4.5976 2.0586 4.5976 4.5976v6.1937c0 2.539-2.0586 4.5976-4.5976 4.5976h-9.1937c-2.539 0-4.5976-2.0586-4.5976-4.5976z"/>
|
||||
<path id="path10" d="m8.85 45c0-1.7397 1.4103-3.15 3.15-3.15h66.5c1.7397 0 3.15 1.4103 3.15 3.15s-1.4103 3.15-3.15 3.15h-66.5c-1.7397 0-3.15-1.4103-3.15-3.15z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
<path id="path12" d="m11.566 0c-6.3876 0-11.566 5.1782-11.566 11.566v14.627c0 1.7713 1.4359 3.2073 3.2072 3.2073s3.2072-1.436 3.2072-3.2073v-14.627c0-2.845 2.3064-5.1514 5.1514-5.1514h14.627c1.7713 0 3.2073-1.4359 3.2073-3.2072s-1.436-3.2072-3.2073-3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
<path id="path14" d="m11.566 90c-6.3876 0-11.566-5.1782-11.566-11.566v-14.628c0-1.7713 1.4359-3.2072 3.2072-3.2072s3.2072 1.4359 3.2072 3.2072v14.628c0 2.845 2.3064 5.1513 5.1514 5.1513h14.627c1.7713 0 3.2073 1.436 3.2073 3.2073 0 1.7712-1.436 3.2072-3.2073 3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
<path id="path16" d="m78.434 0c6.3876 0 11.566 5.1782 11.566 11.566v14.627c0 1.7713-1.4359 3.2073-3.2072 3.2073s-3.2072-1.436-3.2072-3.2073v-14.627c0-2.845-2.3064-5.1514-5.1514-5.1514h-14.627c-1.7713 0-3.2073-1.4359-3.2073-3.2072s1.436-3.2072 3.2073-3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
<path id="path18" d="m78.434 90c6.3876 0 11.566-5.1782 11.566-11.566v-14.628c0-1.7713-1.4359-3.2072-3.2072-3.2072s-3.2072 1.4359-3.2072 3.2072v14.628c0 2.845-2.3064 5.1513-5.1514 5.1513h-14.627c-1.7713 0-3.2073 1.436-3.2073 3.2073 0 1.7712 1.436 3.2072 3.2073 3.2072z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -8,20 +8,27 @@
|
||||
</p>
|
||||
<ul class="md:w-3/4">
|
||||
<% @invitations_unused.each do |invitation| %>
|
||||
<li class="font-mono mb-2 flex gap-1" data-controller="clipboard">
|
||||
<input type="text" disabled class="relative grow"
|
||||
<li class="mb-3 flex gap-1"
|
||||
data-controller="clipboard modal"
|
||||
data-action="keydown.esc->modal#close">
|
||||
<input type="text" disabled class="relative grow font-mono"
|
||||
value="<%= invitation_url(invitation.token) %>"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0 w-auto"
|
||||
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||
data-action="click->modal#open" title="Show QR code">
|
||||
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</button>
|
||||
<%= render QrCodeModalComponent.new(qr_content: invitation_url(invitation.token)) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
58
app/views/rs/oauth/new.html.erb
Normal file
@@ -0,0 +1,58 @@
|
||||
<%= render HeaderCompactComponent.new(title: "Storage") %>
|
||||
|
||||
<%= render MainCompactComponent.new do %>
|
||||
<section class="permissions">
|
||||
<p class="mb-8">
|
||||
The app on
|
||||
<%= link_to @client_id, "https://#{@client_id}", class: "ks-text-link" %>
|
||||
is asking for access to these folders:
|
||||
</p>
|
||||
|
||||
<% if @root_access_requested %>
|
||||
<p class="scope text-lg">
|
||||
<span class="text-red-700">
|
||||
<%= render partial: "icons/alert-triangle",
|
||||
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>
|
||||
All files and directories
|
||||
</span>
|
||||
<% if (@scopes & [":r"]).any? %>
|
||||
<span class="text-sm text-gray-500">(read only)</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<% else %>
|
||||
<% @scopes.each do |scope| %>
|
||||
<p class="scope text-gray-600">
|
||||
<span class="text-lg">
|
||||
<%= render partial: "icons/folder",
|
||||
locals: { custom_class: "inline-block align-bottom mr-1.5" } %>
|
||||
<%= scope_name(scope) %>
|
||||
</span>
|
||||
<% if scope_permissions(scope) == "r" %>
|
||||
<span>(read only)</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= form_with(url: rs_oauth_path, method: :post, data: { turbo: false }) do |f| %>
|
||||
<%= f.hidden_field :redirect_uri, value: @redirect_uri %>
|
||||
<%= f.hidden_field :scope, value: @scopes.join(" ") %>
|
||||
<%= f.hidden_field :user_id, value: @user.id %>
|
||||
<%= f.hidden_field :client_id, value: @client_id %>
|
||||
<%= f.hidden_field :state, value: @state %>
|
||||
<p class="mt-8 mb-6">
|
||||
<%= f.label :expire_at, "Permission expires:", class: "mr-1.5" %>
|
||||
<%= f.select :expire_at, options_for_select(@expire_at_dates) %>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
You can revoke access for this app at any time on your storage dashboard.
|
||||
</p>
|
||||
<p class="mt-8 flex flex-col sm:flex-row gap-3 sm:gap-2 sm:justify-items-stretch">
|
||||
<%= f.submit "Allow",
|
||||
class: "btn-md btn-blue w-full sm:order-last sm:grow",
|
||||
data: { disable_with: "Saving..." } %>
|
||||
<%= link_to "Deny", @denial_url, class: "btn-md btn-gray text-red-700 w-full sm:grow" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
199
app/views/services/chat/show.html.erb
Normal file
@@ -0,0 +1,199 @@
|
||||
<%= render HeaderComponent.new(title: "Chat") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<p class="mb-6">
|
||||
Chat with anyone on the open Jabber (XMPP) network. Message people directly, or
|
||||
join public channels or private rooms.
|
||||
</p>
|
||||
</section>
|
||||
<section data-controller="modal" data-action="keydown.esc->modal#close">
|
||||
<h3>Your Chat Address</h3>
|
||||
<p class="mb-6">
|
||||
When you exchange contacts with people, give them your
|
||||
address, or add them using their address:
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= current_user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||
data-action="click->modal#open" title="Show QR code">
|
||||
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</button>
|
||||
</p>
|
||||
<%= render QrCodeModalComponent.new(qr_content: "xmpp:"+current_user.address) %>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Chat Apps</h3>
|
||||
<p>
|
||||
Use your account with many different apps, and on any devices you wish!
|
||||
When opening an app for the first time, just enter your user address and
|
||||
password to log in.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Recommended Apps</h3>
|
||||
<div data-controller="tabs"
|
||||
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
|
||||
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
|
||||
class="mb-12">
|
||||
<select data-action="tabs#change" data-tabs-target="select"
|
||||
class="block w-full mb-8 sm:hidden">
|
||||
<optgroup label="Mobile">
|
||||
<option>Android</option>
|
||||
<option>iOS</option>
|
||||
</optgroup>
|
||||
<optgroup label="Desktop">
|
||||
<option>Linux</option>
|
||||
<option>Windows</option>
|
||||
<option>macOS</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Android
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
iOS
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Linux
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Windows
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
macOS
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change"> -->
|
||||
<!-- <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> -->
|
||||
<!-- Web -->
|
||||
<!-- </a> -->
|
||||
<!-- </li> -->
|
||||
</ul>
|
||||
|
||||
<div id="apps-android" class="hidden grid grid-cols-1 gap-6"
|
||||
data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Conversations",
|
||||
description: "The gold standard for Jabber on mobile devices",
|
||||
icon_path: "/img/logos/icon_conversations.png",
|
||||
links: [
|
||||
["Website", "https://conversations.im"],
|
||||
["Google Play", "https://play.google.com/store/apps/details?id=eu.siacs.conversations"],
|
||||
["F-Droid", "https://f-droid.org/en/packages/eu.siacs.conversations/"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div id="apps-ios" class="hidden grid grid-cols-1 gap-6"
|
||||
data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Siskin IM",
|
||||
description: "Lightweight and powerful chat app for iPhone and iPad",
|
||||
icon_path: "/img/logos/logo_siskin.png",
|
||||
links: [
|
||||
["Website", "https://siskin.im"],
|
||||
["App Store", "https://apps.apple.com/us/app/tigase-messenger/id1153516838"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Monal",
|
||||
description: "A chat app for iOS, iPadOS, and macOS",
|
||||
icon_path: "/img/logos/icon_monal.svg",
|
||||
icon_fill_box: true,
|
||||
links: [
|
||||
["Website", "https://monal-im.org"],
|
||||
["App Store", "https://apps.apple.com/app/id317711500"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div id="apps-linux" class="hidden grid grid-cols-1 gap-6"
|
||||
data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Dino",
|
||||
description: "A modern and simple chat app for Linux (good for GNOME)",
|
||||
icon_path: "/img/logos/icon_dino.svg",
|
||||
links: [
|
||||
["Website", "https://dino.im"],
|
||||
["Install from package", "https://github.com/dino/dino/wiki/Distribution-Packages"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Kaidan",
|
||||
description: "A fairly new, user-friendly chat app for all devices (good for KDE)",
|
||||
icon_path: "/img/logos/icon_kaidan.svg",
|
||||
links: [
|
||||
["Website", "https://kaidan.im"],
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Gajim",
|
||||
description: "A fully-featured chat app for Linux and Windows",
|
||||
icon_path: "/img/logos/icon_gajim.png",
|
||||
links: [
|
||||
["Website", "https://gajim.org/"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
|
||||
data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Gajim",
|
||||
description: "A fully-featured chat app for Linux and Windows",
|
||||
icon_path: "/img/logos/icon_gajim.png",
|
||||
links: [
|
||||
["Website", "https://gajim.org/"],
|
||||
["Microsoft Store", "https://apps.microsoft.com/store/detail/9PGGF6HD43F9?launch=true&mode=mini"],
|
||||
["Download options", "https://gajim.org/download/"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div id="apps-mac" class="hidden grid grid-cols-1 gap-6"
|
||||
data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Beagle IM",
|
||||
description: "Lightweight and powerful chat app for macOS",
|
||||
icon_path: "/img/logos/logo_beagle.png",
|
||||
links: [
|
||||
["Website", "https://beagle.im"],
|
||||
["App Store", "https://apps.apple.com/us/app/beagleim-by-tigase-inc/id1445349494"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Monal",
|
||||
description: "A chat app for iOS, iPadOS, and macOS",
|
||||
icon_path: "/img/logos/icon_monal.svg",
|
||||
icon_fill_box: true,
|
||||
links: [
|
||||
["Website", "https://monal-im.org"],
|
||||
["App Store", "https://apps.apple.com/app/id1637078500"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<!-- <div class="hidden grid grid-cols-1 gap-4 sm:gap-6" data-tabs-target="panel"> -->
|
||||
<!-- Web -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -16,20 +16,20 @@
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= current_user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section data-controller="modal" data-action="keydown.esc->modal#close">
|
||||
<h3>Wallet Apps</h3>
|
||||
<p>
|
||||
You can connect various wallet apps to your Kosmos account. This allows
|
||||
@@ -40,19 +40,16 @@
|
||||
</p>
|
||||
<p data-controller="clipboard" class="my-6 text-center md:text-left">
|
||||
<input type="text" disabled class="hidden" aria-hidden=true
|
||||
value="<%= @wallet_url%>" data-clipboard-target="source" />
|
||||
value="<%= @wallet_setup_url %>" data-clipboard-target="source" />
|
||||
<button id="copy-setup-code" class="btn-md btn-blue w-full sm:w-auto"
|
||||
data-action="clipboard#copy" data-clipboard-target="trigger">
|
||||
<span class="content-initial">Copy setup code/URL</span>
|
||||
<span class="content-active hidden">Copied ✔</span>
|
||||
</button>
|
||||
<span class="mx-2 my-2 md:my-0 block md:inline">or</span>
|
||||
<button id="show-setup-code" class="btn-md btn-blue w-full sm:w-auto">Show setup QR code</button>
|
||||
<button id="hide-setup-code" class="hidden btn-md btn-blue w-full sm:w-auto">Hide setup QR code</button>
|
||||
</p>
|
||||
<p id="setup-code" class="hidden my-10 w-full text-center">
|
||||
<%= raw @lndhub_qr_svg %>
|
||||
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">Show setup QR code</button>
|
||||
</p>
|
||||
<%= render QrCodeModalComponent.new(qr_content: @wallet_setup_url) %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -119,27 +116,3 @@
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
const buttonShow = document.querySelector('#show-setup-code');
|
||||
const buttonHide = document.querySelector('#hide-setup-code');
|
||||
const setupCode = document.querySelector('#setup-code');
|
||||
|
||||
buttonShow.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
setupCode.classList.remove('hidden');
|
||||
buttonHide.classList.remove('hidden');
|
||||
buttonShow.classList.add('hidden');
|
||||
setupCode.scrollIntoView({behavior: "smooth", block: "nearest"});
|
||||
});
|
||||
|
||||
buttonHide.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
const el = document.querySelector('#setup-code');
|
||||
setupCode.classList.add('hidden');
|
||||
buttonHide.classList.add('hidden');
|
||||
buttonShow.classList.remove('hidden');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
219
app/views/services/mastodon/show.html.erb
Normal file
@@ -0,0 +1,219 @@
|
||||
<%= render HeaderComponent.new(title: "Social") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<p class="mb-6">
|
||||
Follow and interact with anyone on the open social web, from your Kosmos Mastodon account.
|
||||
</p>
|
||||
</section>
|
||||
<section data-controller="modal" data-action="keydown.esc->modal#close">
|
||||
<h3>Your User Address</h3>
|
||||
<p class="mb-6">
|
||||
Others can follow you under this address:
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= current_user.mastodon_address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||
data-action="click->modal#open" title="Show QR code">
|
||||
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</button>
|
||||
</p>
|
||||
<%= render QrCodeModalComponent.new(qr_content: current_user.address) %>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Social Apps</h3>
|
||||
<p>
|
||||
Use your Mastodon account with many different apps, and on any devices
|
||||
you wish! When adding your account to an app, you will log in via
|
||||
<a href="https://kosmos.social" target="_blank" class="ks-text-link">kosmos.social</a>.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Recommended Apps</h3>
|
||||
<div data-controller="tabs"
|
||||
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
|
||||
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
|
||||
class="mb-12">
|
||||
<select data-action="tabs#change" data-tabs-target="select"
|
||||
class="block w-full mb-8 sm:hidden">
|
||||
<option>Web</option>
|
||||
<optgroup label="Mobile">
|
||||
<option>Android</option>
|
||||
<option>iOS</option>
|
||||
</optgroup>
|
||||
<optgroup label="Desktop">
|
||||
<option>Linux</option>
|
||||
<option>Windows</option>
|
||||
<option>macOS</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Web
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-5 font-semibold no-underline">
|
||||
Android
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
iOS
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Linux
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
Windows
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||
macOS
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "kosmos.social",
|
||||
description: "The official Web app",
|
||||
icon_path: "/img/logos/icon_mastodon-2.svg",
|
||||
links: [
|
||||
["Launch", "https://kosmos.social"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Elk",
|
||||
description: " A nimble Mastodon web client",
|
||||
icon_path: "/img/logos/icon_elk.svg",
|
||||
links: [
|
||||
["Launch", "https://elk.zone"],
|
||||
["GitHub", "https://github.com/elk-zone/elk"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Sengi",
|
||||
description: "A cross-platform app, inspired by TweetDeck",
|
||||
icon_path: "/img/logos/icon_sengi.png",
|
||||
links: [
|
||||
["Website", "https://nicolasconstant.github.io/sengi/"],
|
||||
["GitHub", "https://github.com/NicolasConstant/sengi"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Mastodon for Android",
|
||||
description: "Android client by the Mastodon core team",
|
||||
icon_path: "/img/logos/icon_mastodon-2.svg",
|
||||
links: [
|
||||
["Website", "https://joinmastodon.org/apps"],
|
||||
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Fedilab",
|
||||
description: "Android client with many features",
|
||||
icon_path: "/img/logos/icon_fedilab.png",
|
||||
links: [
|
||||
["Website", "https://fedilab.app"],
|
||||
["Google Play", "https://play.google.com/store/apps/details?id=app.fedilab.android"],
|
||||
["F-Droid", "https://f-droid.org/packages/fr.gouv.etalab.mastodon"],
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Megalodon",
|
||||
description: "A popular fork of the official Android app",
|
||||
icon_path: "/img/logos/icon_megalodon.png",
|
||||
icon_fill_box: true,
|
||||
links: [
|
||||
["Website", "https://sk22.github.io/megalodon/"],
|
||||
["Google Play", "https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Mastodon for iOS",
|
||||
description: "iOS client by the Mastodon core team",
|
||||
icon_path: "/img/logos/icon_mastodon-2.svg",
|
||||
links: [
|
||||
["Website", "https://joinmastodon.org/apps"],
|
||||
["App Store", "https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Ice Cubes",
|
||||
description: "Slick, fast, open source, and with customizable UI",
|
||||
icon_path: "/img/logos/icon_icecubes.png",
|
||||
icon_fill_box: true,
|
||||
links: [
|
||||
["App Store", "https://apps.apple.com/us/app/ice-cubes-for-mastodon/id6444915884"],
|
||||
["GitHub", "https://github.com/Dimillian/IceCubesApp"]
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Mammoth",
|
||||
description: " Powerful, fast, feature-rich",
|
||||
icon_path: "/img/logos/icon_mammoth.png",
|
||||
links: [
|
||||
["Website", "https://getmammoth.app/"],
|
||||
["App Store", "https://apps.apple.com/app/mammoth-for-mastodon/id1667573899"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Tuba",
|
||||
description: "A simple, fast Mastodon app for Linux (good on GNOME)",
|
||||
icon_path: "/img/logos/icon_tuba.svg",
|
||||
links: [
|
||||
["Website", "https://tuba.geopjr.dev"],
|
||||
["Flathub", "https://flathub.org/apps/dev.geopjr.Tuba"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Sengi",
|
||||
description: "A cross-platform app, inspired by TweetDeck",
|
||||
icon_path: "/img/logos/icon_sengi.png",
|
||||
links: [
|
||||
["Website", "https://nicolasconstant.github.io/sengi/"],
|
||||
["GitHub", "https://github.com/NicolasConstant/sengi"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Mastonaut",
|
||||
description: "Simple, elegant, and native Mastodon client for Mac",
|
||||
icon_path: "/img/logos/icon_mastonaut.png",
|
||||
links: [
|
||||
["Launch", "https://www.mastonaut.app"],
|
||||
["Mac App Store", "https://apps.apple.com/app/mastonaut/id1450757574"]
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -13,13 +13,13 @@
|
||||
'settings--account--email-target': 'emailField'
|
||||
}, required: true %>
|
||||
<button type="button" id="edit-email"
|
||||
class="btn-md btn-icon btn-blue shrink-0 hidden initial-visible"
|
||||
class="btn-md btn-icon btn-outline shrink-0 hidden initial-visible"
|
||||
data-settings--account--email-target="editEmailButton"
|
||||
data-action="settings--account--email#editEmail"
|
||||
title="Edit email address">
|
||||
<span class="">
|
||||
<%= render partial: "icons/edit-3", locals: {
|
||||
custom_class: "text-white h-4 w-4 inline" } %>
|
||||
custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@@ -1,33 +1,62 @@
|
||||
<section>
|
||||
<h3>Profile</h3>
|
||||
<p class="mb-2">
|
||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= @user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-blue shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Your user address for Chat and Lightning Network.
|
||||
</p>
|
||||
<div class="mb-6">
|
||||
<p class="mb-2">
|
||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= @user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Your user address for Chat and Lightning Network.
|
||||
</p>
|
||||
</div>
|
||||
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
|
||||
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
|
||||
<%= f.text_field :display_name, class: "w-full sm:w-3/5 mb-2" %>
|
||||
<%= f.text_field :display_name, class: "w-full sm:w-3/5" %>
|
||||
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
|
||||
<p class="error-msg"><%= @validation_errors[:display_name].first %></p>
|
||||
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<label class="block">
|
||||
<p class="font-bold mb-1">
|
||||
Avatar
|
||||
</p>
|
||||
<p class="text-gray-500">
|
||||
Default profile picture
|
||||
</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<% if current_user.avatar.present? %>
|
||||
<p class="flex-none">
|
||||
<%= image_tag "data:image/jpeg;base64,#{current_user.avatar}", class: "h-24 w-24 rounded-lg" %>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="grow">
|
||||
<p class="mb-2">
|
||||
<%= f.file_field :avatar, class: "" %>
|
||||
<p class="text-sm text-gray-500">
|
||||
JPEG or PNG image, not larger than 1 megabyte
|
||||
</p>
|
||||
<% if @validation_errors.present? && @validation_errors[:avatar].present? %>
|
||||
<p class="error-msg mb-2"><%= @validation_errors[:avatar].first %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
|
||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||
</p>
|
||||
|
||||
@@ -1,56 +1,70 @@
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "BTCPay",
|
||||
path: admin_settings_services_path(params: { s: "btcpay" }),
|
||||
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "btcpay" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Discourse",
|
||||
path: admin_settings_services_path(params: { s: "discourse" }),
|
||||
icon: Setting.discourse_enabled? ? "check" : "x",
|
||||
text_icon: Setting.discourse_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Drone CI",
|
||||
path: admin_settings_services_path(params: { s: "droneci" }),
|
||||
text_icon: Setting.droneci_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "ejabberd",
|
||||
path: admin_settings_services_path(params: { s: "ejabberd" }),
|
||||
icon: Setting.ejabberd_enabled? ? "check" : "x",
|
||||
text_icon: Setting.ejabberd_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Gitea",
|
||||
path: admin_settings_services_path(params: { s: "gitea" }),
|
||||
icon: Setting.gitea_enabled? ? "check" : "x",
|
||||
text_icon: Setting.gitea_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "gitea" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "LNDHub",
|
||||
path: admin_settings_services_path(params: { s: "lndhub" }),
|
||||
icon: Setting.lndhub_enabled? ? "check" : "x",
|
||||
text_icon: Setting.lndhub_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "lndhub" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Mastodon",
|
||||
path: admin_settings_services_path(params: { s: "mastodon" }),
|
||||
icon: Setting.mastodon_enabled? ? "check" : "x",
|
||||
text_icon: Setting.mastodon_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "mastodon" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "MediaWiki",
|
||||
path: admin_settings_services_path(params: { s: "mediawiki" }),
|
||||
icon: Setting.mediawiki_enabled? ? "check" : "x",
|
||||
text_icon: Setting.mediawiki_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Nostr",
|
||||
path: admin_settings_services_path(params: { s: "nostr" }),
|
||||
icon: Setting.nostr_enabled? ? "check" : "x",
|
||||
text_icon: Setting.nostr_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "nostr" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "RemoteStorage",
|
||||
path: admin_settings_services_path(params: { s: "remotestorage" }),
|
||||
icon: Setting.remotestorage_enabled? ? "check" : "x",
|
||||
text_icon: Setting.remotestorage_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })),
|
||||
) %>
|
||||
|
||||
6
app/views/shared/status_bad_request.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<%= render HeaderCompactComponent.new(title: "404") %>
|
||||
|
||||
<%= render MainCompactComponent.new do %>
|
||||
<h2>Bad request</h2>
|
||||
<p>Please go back and try again.</p>
|
||||
<% end %>
|
||||
4
ci/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM guildeducation/rails:2.7.2-14.20.0
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends ldap-utils libvips
|
||||
@@ -5,7 +5,7 @@ require "rails"
|
||||
require "active_model/railtie"
|
||||
require "active_job/railtie"
|
||||
require "active_record/railtie"
|
||||
# require "active_storage/engine"
|
||||
require "active_storage/engine"
|
||||
require "action_controller/railtie"
|
||||
require "action_mailer/railtie"
|
||||
require "action_mailbox/engine"
|
||||
|
||||
@@ -1 +1 @@
|
||||
yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg==
|
||||
tmI5vm7qZhaigr52jEBVWkRdj+EE+9OmPh3vWXC7kA/OHuuucpr7SodychuMkQDPLM0BLk88LFsqvRIR+mqnLWpRC+P9aeUFE6ohxSWzcAd7Y4sgxUD8zpCRPndrwTw0hxXXj1WZSYeWn4BoAB34aV+gYen2MajZF3a95hJGtS5yjgWxvLVkQQKqRDfykkfX6fCS0BPo5X7sT7m4xwCATD/D4219wajm5W3TIdkriHtwt28ZLspaRWA5e0UkzKf8+/Gaj2CrW7UWcvew8R93zQ5RA2/Sp3sDTVN+kLz9I9Q095lQC0ywCAEFYHeKmc2tjrzqRaAAWu06xmWLqGIg21G+A/UU9lUJOkIpxQACWoOfS2IoXR1nXhgXMopkz3aCBXDxKw554v4H2QyOceOsuRf2C685ibMqzQkKMmJ4tcbiOJL77DUc08JTjB8Dq4Ohr8sMzXbV/hATevjYoRP0XarLekqhLv90ZLuIVY16DwB0CzACeNBKeKbeLqJF51upRRWgi+gTbYpV04yUwnXdyssF8mydWocgihrTryBi8F6PsuhBGcaYdP+0yibnGxDCC4x2rupbBfMj2OIX7pYzgtIHB3Eo954Y+bCoggqbE/Qrb9VVXNMgtKgLt8EGWU2tg6wl9QicitIq87uLDAade93zTn6rmcKPywjMDo6jbVIs653ZdUhiKdHGdpnJccbgQ/iLSPB1umNnCeaEX5jM+K9zBvl7ZMCdSk1YIQ==--ekKumqLiSlVJNwMe--K/ecXmmMT1x+WnIXMbHBDw==
|
||||
@@ -70,4 +70,7 @@ Rails.application.configure do
|
||||
|
||||
# Allow requests from any IP
|
||||
config.web_console.whiny_requests = false
|
||||
|
||||
# Store attachments on the local disk (in ./storage)
|
||||
config.active_storage.service = :local
|
||||
end
|
||||
|
||||
@@ -110,6 +110,10 @@ Rails.application.configure do
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
config.action_mailer.raise_delivery_errors = true
|
||||
|
||||
# TODO make configurable
|
||||
# Store attachments in S3-compatible back-end
|
||||
config.active_storage.service = :local
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
@@ -51,4 +51,7 @@ Rails.application.configure do
|
||||
}
|
||||
|
||||
config.active_job.queue_adapter = :test
|
||||
|
||||
# Store attachments on the local disk (in ./tmp)
|
||||
config.active_storage.service = :test
|
||||
end
|
||||
|
||||
@@ -6,3 +6,4 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
||||
pin "bech32" # @2.0.0
|
||||
pin "tailwindcss-stimulus-components" # @4.0.3
|
||||
|
||||
@@ -21,12 +21,16 @@ Rails.application.routes.draw do
|
||||
namespace :services do
|
||||
get 'storage', to: 'remotestorage#dashboard'
|
||||
|
||||
resource :chat, only: [:show], controller: 'chat'
|
||||
|
||||
resources :lightning, only: [:index] do
|
||||
collection do
|
||||
get 'transactions'
|
||||
get 'qr_lnurlp'
|
||||
end
|
||||
end
|
||||
|
||||
resource :mastodon, only: [:show], controller: 'mastodon'
|
||||
end
|
||||
|
||||
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
|
||||
@@ -50,7 +54,8 @@ Rails.application.routes.draw do
|
||||
post 'webhooks/lndhub', to: 'webhooks#lndhub'
|
||||
|
||||
namespace :api do
|
||||
get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance'
|
||||
get 'btcpay/onchain_btc_balance', to: 'btcpay#onchain_btc_balance'
|
||||
get 'btcpay/lightning_btc_balance', to: 'btcpay#lightning_btc_balance'
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
@@ -66,7 +71,14 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
get ".well-known/webfinger", to: 'webfinger#show'
|
||||
namespace :rs do
|
||||
resource :oauth, only: [:new, :create], path_names: {
|
||||
new: ':useraddress', create: ':useraddress'
|
||||
}, controller: 'oauth', constraints: { useraddress: /[^\/]+/}
|
||||
get 'oauth/token/:id/launch_app' => 'oauth#launch_app', as: :launch_app
|
||||
end
|
||||
|
||||
get '.well-known/webfinger', to: 'webfinger#show'
|
||||
|
||||
namespace :discourse do
|
||||
get "connect", to: 'sso#connect'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
test:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("tmp/storage") %>
|
||||
|
||||
local:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("storage") %>
|
||||
|
||||
test:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("tmp/storage") %>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
class CreateRemoteStorageAuthorizations < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
db_type = ActiveRecord::Base.configurations.find_db_config(Rails.env).adapter
|
||||
array_default = db_type == "postgresql" ? [] : [].to_yaml
|
||||
|
||||
create_table :remote_storage_authorizations do |t|
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.string :token
|
||||
t.text :permissions, array: true, default: array_default
|
||||
t.string :client_id
|
||||
t.string :redirect_uri
|
||||
t.string :app_name
|
||||
t.datetime :expire_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :remote_storage_authorizations, :permissions, using: 'gin'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
create_table :active_storage_blobs, id: primary_key_type do |t|
|
||||
t.string :key, null: false
|
||||
t.string :filename, null: false
|
||||
t.string :content_type
|
||||
t.text :metadata
|
||||
t.string :service_name, null: false
|
||||
t.bigint :byte_size, null: false
|
||||
t.string :checksum
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments, id: primary_key_type do |t|
|
||||
t.string :name, null: false
|
||||
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
||||
t.references :blob, null: false, type: foreign_key_type
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
|
||||
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
||||
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
||||
t.string :variation_digest, null: false
|
||||
|
||||
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[primary_key_type, foreign_key_type]
|
||||
end
|
||||
end
|
||||
33
db/schema.rb
@@ -10,7 +10,35 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_09_06_073324) do
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.string "service_name", null: false
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum"
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
||||
t.bigint "blob_id", null: false
|
||||
t.string "variation_digest", null: false
|
||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "donations", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "amount_sats"
|
||||
@@ -84,7 +112,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
t.datetime "confirmed_at", precision: nil
|
||||
t.datetime "confirmation_sent_at", precision: nil
|
||||
t.string "unconfirmed_email"
|
||||
t.text "ln_login_ciphertext"
|
||||
t.text "ln_password_ciphertext"
|
||||
t.string "ln_account"
|
||||
t.string "nostr_pubkey"
|
||||
@@ -95,5 +122,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "remote_storage_authorizations", "users"
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@ Sidekiq::Testing.inline! do
|
||||
35.times do |n|
|
||||
username = Faker::Name.unique.first_name.downcase
|
||||
email = Faker::Internet.unique.email
|
||||
next if username.length < 3
|
||||
|
||||
CreateAccount.call(
|
||||
username: username, domain: "kosmos.org", email: email,
|
||||
|
||||
@@ -12,57 +12,62 @@ services:
|
||||
DS_DM_PASSWORD: passthebutter
|
||||
SUFFIX_NAME: "dc=kosmos,dc=org"
|
||||
|
||||
# redis:
|
||||
# restart: always
|
||||
# image: redis:7-alpine
|
||||
# networks:
|
||||
# - internal_network
|
||||
# healthcheck:
|
||||
# test: ['CMD', 'redis-cli', 'ping']
|
||||
# volumes:
|
||||
# - ./tmp/redis:/data
|
||||
redis:
|
||||
restart: always
|
||||
image: redis:7-alpine
|
||||
networks:
|
||||
- internal_network
|
||||
healthcheck:
|
||||
test: ['CMD', 'redis-cli', 'ping']
|
||||
volumes:
|
||||
- ./tmp/redis:/data
|
||||
|
||||
# web:
|
||||
# build: .
|
||||
# tty: true
|
||||
# command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev"
|
||||
# volumes:
|
||||
# - .:/akkounts
|
||||
# networks:
|
||||
# - external_network
|
||||
# - internal_network
|
||||
# ports:
|
||||
# - "3000:3000"
|
||||
# environment:
|
||||
# RAILS_ENV: development
|
||||
# REDIS_URL: redis://redis:6379/0
|
||||
# LDAP_HOST: ldap
|
||||
# LDAP_PORT: 3389
|
||||
# LDAP_ADMIN_PASSWORD: passthebutter
|
||||
# LDAP_USE_TLS: "false"
|
||||
# depends_on:
|
||||
# - ldap
|
||||
# - redis
|
||||
web:
|
||||
build: .
|
||||
tty: true
|
||||
command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev"
|
||||
volumes:
|
||||
- .:/akkounts
|
||||
- /akkounts/node_modules
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
PRIMARY_DOMAIN: kosmos.org
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
LDAP_HOST: ldap
|
||||
LDAP_PORT: 3389
|
||||
LDAP_ADMIN_PASSWORD: passthebutter
|
||||
LDAP_USE_TLS: "false"
|
||||
depends_on:
|
||||
- ldap
|
||||
- redis
|
||||
|
||||
# sidekiq:
|
||||
# build: .
|
||||
# command: bash -c "bundle exec sidekiq -C config/sidekiq.yml"
|
||||
# volumes:
|
||||
# - .:/akkounts
|
||||
# networks:
|
||||
# - internal_network
|
||||
# environment:
|
||||
# RAILS_ENV: development
|
||||
# REDIS_URL: redis://redis:6379/0
|
||||
# LDAP_HOST: ldap
|
||||
# LDAP_PORT: 3389
|
||||
# LDAP_ADMIN_PASSWORD: passthebutter
|
||||
# LDAP_USE_TLS: "false"
|
||||
# LAUNCHY_DRY_RUN: true
|
||||
# BROWSER: /dev/null
|
||||
# depends_on:
|
||||
# - ldap
|
||||
# - redis
|
||||
sidekiq:
|
||||
build: .
|
||||
command: bash -c "bundle exec sidekiq -C config/sidekiq.yml"
|
||||
volumes:
|
||||
- .:/akkounts
|
||||
networks:
|
||||
- internal_network
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
PRIMARY_DOMAIN: kosmos.org
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
LDAP_HOST: ldap
|
||||
LDAP_PORT: 3389
|
||||
LDAP_ADMIN_PASSWORD: passthebutter
|
||||
LDAP_USE_TLS: "false"
|
||||
LAUNCHY_DRY_RUN: true
|
||||
BROWSER: /dev/null
|
||||
depends_on:
|
||||
- ldap
|
||||
- redis
|
||||
|
||||
# phpldapadmin:
|
||||
# image: osixia/phpldapadmin:0.9.0
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"postcss-preset-env": "^7.8.3",
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.1",
|
||||
"scripts": {
|
||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||
"build:css": "yarn run build:css:tailwind"
|
||||
|
||||
BIN
public/img/logos/icon_conversations.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
1
public/img/logos/icon_dino.svg
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
42
public/img/logos/icon_elk.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="250"
|
||||
height="250"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg30"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs34" />
|
||||
<mask
|
||||
id="a"
|
||||
width="240"
|
||||
height="234"
|
||||
x="4"
|
||||
y="1"
|
||||
maskUnits="userSpaceOnUse"
|
||||
style="mask-type:alpha">
|
||||
<path
|
||||
fill="white"
|
||||
d="M244 123c0 64.617-38.383 112-103 112-64.617 0-103-30.883-103-95.5C38 111.194-8.729 36.236 8 16 29.46-9.959 88.689 6 125 6c64.617 0 119 52.383 119 117Z"
|
||||
id="path19" />
|
||||
</mask>
|
||||
<g
|
||||
mask="url(#a)"
|
||||
id="g28"
|
||||
transform="matrix(0.90923731,0,0,1.0049564,13.520015,-3.1040835)">
|
||||
<path
|
||||
fill="#ea9e44"
|
||||
d="m 116.94,88.1 c -13.344,1.552 -20.436,-2.019 -24.706,10.71 0,0 14.336,21.655 52.54,21.112 -2.135,8.848 -1.144,15.368 -1.144,23.207 0,26.079 -20.589,48.821 -65.961,48.821 -23.03,0 -51.015,4.191 -72.367,15.911 -15.175,8.305 -27.048,20.336 -32.302,37.023 l 5.956,8.461 11.4,0.155 v 47.889 l -13.91,21.966 3.998,63.645 H -6.364 L -5.22,335.773 C 1.338,331.892 16.36,321.802 29.171,306.279 46.557,285.4 59.902,255.052 44.193,217.486 l 11.744,-5.045 c 12.887,30.814 8.388,57.514 -2.898,79.013 21.58,-0.698 40.11,-2.095 55.819,-4.734 l -3.584,-43.698 12.659,-1.087 L 129.98,387 h 13.116 l 2.212,-94.459 c 10.447,-4.502 34.239,-21.034 45.372,-78.47 1.372,-6.986 2.135,-12.885 2.516,-17.93 1.754,-12.806 2.745,-27.243 3.051,-43.698 l -18.683,-5.976 h 57.42 l 5.567,-12.807 c -5.414,0.233 -11.896,-2.639 -11.896,-2.639 l 1.297,-6.209 H 242 L 176.801,90.428 c -7.244,2.794 -14.87,6.442 -20.208,10.866 -4.27,-3.105 -19.063,-12.807 -39.653,-13.195 z"
|
||||
id="path22" />
|
||||
<path
|
||||
fill="#c16929"
|
||||
d="M 6.217,24.493 18.494,21 c 5.948,21.577 13.345,33.375 22.648,39.352 8.388,5.099 19.75,5.239 31.799,4.579 C 69.433,63.767 66.154,62.137 63.104,59.886 56.317,54.841 50.522,46.458 46.175,31.246 l 12.201,-3.649 c 3.279,11.488 7.092,18.085 12.201,21.888 5.11,3.726 11.286,4.657 18.606,5.433 13.726,1.553 30.884,2.174 52.312,12.264 2.898,1.086 5.872,2.483 8.769,4.036 -0.381,-0.776 -0.762,-1.553 -1.296,-2.406 -3.66,-5.822 -10.828,-11.953 -24.097,-16.92 l 4.27,-12.109 c 21.581,7.917 30.121,19.171 33.553,28.097 3.965,10.168 1.525,18.124 1.525,18.124 -3.05,1.009 -6.1,2.406 -9.608,3.492 -6.634,-4.579 -12.887,-8.033 -18.835,-10.75 C 113.814,70.442 92.31,76.108 73.246,77.893 58.91,79.213 45.794,78.591 34.432,71.295 23.222,64.155 13.385,50.495 6.217,24.493 Z"
|
||||
id="path24" />
|
||||
<path
|
||||
fill="#c16929"
|
||||
d="M 90.098,45.294 C 87.582,39.55 86.057,32.487 86.743,23.794 l 12.659,0.932 c -0.763,10.555 2.897,17.696 7.015,22.353 -5.338,-0.931 -10.447,-1.04 -16.319,-1.785 z m 80.069,-1.32 8.312,-9.702 c 21.58,19.094 8.159,46.415 8.159,46.415 l -11.819,-1.32 c -0.382,-6.24 -1.144,-17.836 -6.635,-24.371 3.584,1.84 6.635,3.865 9.99,6.908 0,-5.666 -1.754,-12.341 -8.007,-17.93 z"
|
||||
id="path26" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/img/logos/icon_fedilab.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/img/logos/icon_gajim.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/logos/icon_icecubes.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
1
public/img/logos/icon_kaidan.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="256.39" width="256.39"><defs><radialGradient xlink:href="#b" id="g" cx="97.347" cy="925.562" fx="97.347" fy="925.562" r="59.347" gradientTransform="matrix(0 .94357 -2.19206 0 2158.799 18.862)" gradientUnits="userSpaceOnUse"/><radialGradient xlink:href="#b" id="h" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .8425 -1.93775 0 1946.71 -14.002)" cx="95.806" cy="937.536" fx="95.806" fy="937.536" r="59.347"/><radialGradient id="a" gradientUnits="userSpaceOnUse" cy="256" cx="256" gradientTransform="matrix(2.2585 .02063 -.02134 2.336 -316.73 -347.29)" r="249.14"><stop stop-color="#30c357" offset="0"/><stop offset=".5" stop-color="#2cb465"/><stop stop-color="#32a482" offset="1"/></radialGradient><radialGradient xlink:href="#b" id="f" cx="88.754" cy="123.035" fx="88.754" fy="123.035" r="78.3" gradientTransform="matrix(.02885 1.09003 -1.35878 .03597 300.127 33.098)" gradientUnits="userSpaceOnUse"/><linearGradient id="b"><stop offset="0" stop-opacity=".416"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="e" gradientUnits="userSpaceOnUse" x1="127.988" y1="808.969" x2="127.988" y2="1033.499" gradientTransform="translate(18.596 -806.496) scale(1.011)"/><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" x1="127.988" y1="808.969" x2="127.988" y2="1033.499" gradientTransform="translate(18.596 -806.496) scale(1.011)"/><filter id="c" x="-.041" width="1.082" y="-.04" height="1.081" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="3.891"/></filter></defs><g transform="translate(82 -2.286)" opacity=".512" fill-opacity=".509" filter="url(#c)"><path d="M47.992 16.371c-62.684 0-113.5 50.816-113.5 113.5 0 15.92 3.284 31.071 9.201 44.822 2.875 7.87 5.724 15.722 6.72 22.264 1.01 6.646.108 11.944-1.16 15.361-1.266 3.418-2.901 4.955-4.165 8.502-1.264 3.548-2.155 9.106.775 13.319 2.93 4.213 9.68 7.08 22.17 9.277 12.49 2.197 30.725 3.719 46.623 3.719 15.899 0 29.471-1.536 39.037-2.932 8.418-1.228 13.704-2.35 18.643-3.459.557-.122 1.114-.241 1.668-.37l.363-.083-.01-.006c49.969-11.888 87.135-56.807 87.135-110.414 0-62.684-50.816-113.5-113.5-113.5z" fill-opacity="1" paint-order="stroke fill markers"/></g><g fill="#1f6d52"><path d="M241.492 125.586c0 62.684-50.816 113.5-113.5 113.5s-113.5-50.816-113.5-113.5 50.816-113.5 113.5-113.5 113.5 50.816 113.5 113.5z" paint-order="stroke fill markers"/><path d="M154.368 236.005c-5.556 1.258-11.108 2.516-20.674 3.912-9.566 1.397-23.139 2.93-39.037 2.933-15.899 0-34.133-1.524-46.623-3.72-12.49-2.197-19.241-5.064-22.171-9.277-2.93-4.213-2.038-9.77-.774-13.318 1.263-3.548 2.898-5.086 4.165-8.503 1.268-3.418 2.168-8.716 1.158-15.361-1.011-6.646-3.932-14.64-6.853-22.634"/></g><path d="M261.492 124.872c0 62.684-50.816 113.5-113.5 113.5s-113.5-50.816-113.5-113.5c0-62.685 50.816-113.5 113.5-113.5s113.5 50.815 113.5 113.5z" fill="url(#d)" paint-order="stroke fill markers" transform="translate(-20 -4.286)"/><path d="M174.368 235.29c-5.556 1.26-11.108 2.517-20.674 3.913-9.566 1.396-23.139 2.93-39.037 2.933-15.899 0-34.133-1.524-46.623-3.72-12.49-2.197-19.241-5.064-22.101-8.228-2.86-3.163-1.828-6.622-.565-10.17 1.264-3.547 2.759-7.184 3.956-11.651 1.198-4.467 2.098-9.765 1.088-16.41-1.011-6.646-3.932-14.64-6.853-22.635" fill="url(#e)" transform="translate(-20 -4.286)"/><path d="M188.207 160.324l-82.176 19.83-19.979-20.038S72.97 173.377 67.98 179.894l46.02 46.82 14.242 7.371c10.099-1.39 19.726-3.117 28.797-5.267.013 0 .026-.01.04-.01 27.199-7.275 48.656-19.338 63.52-36.12z" fill="url(#f)"/><path d="M205.473 113.95L49.639 132.596l30 30 147.375 21.975c7.02-9.995 12.02-21.406 14.867-34.211z" fill="url(#g)"/><path d="M188.46 68.752l45 45-142.557-2.826-24-24z" fill="url(#h)"/><g transform="translate(-88.78 -687.44) scale(1.011)" fill="#c1ede5"><rect x="150.87" y="744.15" width="127.26" height="26.476" ry="13.238" rx="13.238"/><rect x="133.62" y="789.12" width="161.61" height="26.476" ry="13.238" rx="13.238"/><rect x="186.23" y="834.92" width="91.833" height="26.476" ry="13.238" rx="13.238"/><rect x="150.8" y="834.92" width="26.476" height="26.476" ry="13.238" rx="13.238"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/img/logos/icon_mammoth.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
65
public/img/logos/icon_mastodon-2.svg
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg13"
|
||||
sodipodi:docname="icon_mastodon-2.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview15"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.329114"
|
||||
inkscape:cx="25.849265"
|
||||
inkscape:cy="39.548407"
|
||||
inkscape:window-width="1663"
|
||||
inkscape:window-height="749"
|
||||
inkscape:window-x="50"
|
||||
inkscape:window-y="118"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg13" />
|
||||
<a
|
||||
id="a300"
|
||||
transform="translate(12.930783,10.499984)">
|
||||
<path
|
||||
d="M 73.8393,17.4898 C 72.6973,9.00165 65.2994,2.31235 56.5296,1.01614 55.05,0.797115 49.4441,0 36.4582,0 h -0.097 C 23.3717,0 20.585,0.797115 19.1054,1.01614 10.5798,2.27644 2.79399,8.28712 0.904997,16.8758 -0.00358524,21.1056 -0.100549,25.7949 0.0682394,30.0965 0.308852,36.2651 0.355538,42.423 0.91577,48.5665 c 0.3873,4.0809 1.06295,8.1292 2.02186,12.1147 1.79562,7.3608 9.06427,13.4864 16.18567,15.9854 7.6245,2.6062 15.8241,3.0389 23.6806,1.2496 0.8643,-0.2011 1.7178,-0.4345 2.5606,-0.7002 1.9105,-0.6068 4.1478,-1.2855 5.7926,-2.4775 0.0226,-0.0168 0.0411,-0.0384 0.0541,-0.0632 0.0131,-0.0249 0.0204,-0.0524 0.0213,-0.0805 v -5.9532 c -4e-4,-0.0262 -0.0066,-0.052 -0.0183,-0.0755 -0.0117,-0.0235 -0.0284,-0.0441 -0.0491,-0.0603 -0.0207,-0.0162 -0.0447,-0.0275 -0.0703,-0.0332 -0.0256,-0.0057 -0.0522,-0.0056 -0.0777,3e-4 -5.0336,1.2021 -10.1917,1.8048 -15.3669,1.7953 -8.9063,0 -11.3016,-4.2262 -11.9876,-5.9856 -0.5513,-1.5206 -0.9014,-3.1067 -1.0414,-4.718 -0.0015,-0.0271 0.0035,-0.0541 0.0145,-0.0789 0.0109,-0.0248 0.0276,-0.0466 0.0486,-0.0637 0.021,-0.0172 0.0457,-0.0291 0.0722,-0.0349 0.0265,-0.0058 0.0539,-0.0053 0.0802,0.0015 4.9497,1.194 10.0237,1.7967 15.1155,1.7953 1.2246,0 2.4456,0 3.6702,-0.0323 5.1211,-0.1436 10.5187,-0.4057 15.5572,-1.3895 0.1257,-0.0252 0.2514,-0.0467 0.3591,-0.079 7.9474,-1.5261 15.5106,-6.3159 16.2791,-18.445 0.0287,-0.4775 0.1006,-5.0017 0.1006,-5.4972 0.0035,-1.684 0.5422,-11.946 -0.0791,-18.2511 z"
|
||||
fill="url(#paint0_linear_549_34)"
|
||||
id="path2"
|
||||
style="fill:url(#paint0_linear_549_34)" />
|
||||
<path
|
||||
d="M 61.2484,27.0263 V 48.114 H 52.8916 V 27.6475 c 0,-4.3087 -1.7956,-6.5062 -5.4479,-6.5062 -4.015,0 -6.026,2.5996 -6.026,7.7342 V 40.0782 H 33.1111 V 28.8755 c 0,-5.1346 -2.0146,-7.7342 -6.0296,-7.7342 -3.6308,0 -5.4444,2.1975 -5.4444,6.5062 V 48.114 H 13.2839 V 27.0263 c 0,-4.3087 1.1001,-7.7317 3.3004,-10.2691 2.2696,-2.5314 5.2468,-3.8312 8.9421,-3.8312 4.2772,0 7.5093,1.6445 9.6641,4.9299 l 2.0793,3.4901 2.0829,-3.4901 c 2.1547,-3.2854 5.3868,-4.9299 9.6568,-4.9299 3.6918,0 6.6689,1.2998 8.9458,3.8312 2.1978,2.535 3.2955,5.958 3.2931,10.2691 z"
|
||||
fill="#ffffff"
|
||||
id="path4" />
|
||||
</a>
|
||||
<defs
|
||||
id="defs11">
|
||||
<linearGradient
|
||||
id="paint0_linear_549_34"
|
||||
x1="37.069199"
|
||||
y1="0"
|
||||
x2="37.069199"
|
||||
y2="79"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#6364FF"
|
||||
id="stop6" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#563ACC"
|
||||
id="stop8" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |