Finish MVP of remoteStorage service pages/UI #202
2
Gemfile
@ -61,7 +61,7 @@ gem "sentry-rails"
|
||||
# Services
|
||||
gem 'discourse_api'
|
||||
gem "lnurl"
|
||||
gem 'manifique'
|
||||
gem 'manifique', '~> 1.1.0'
|
||||
gem 'nostr', '~> 0.6.0'
|
||||
|
||||
group :development, :test do
|
||||
|
@ -245,7 +245,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
manifique (1.0.1)
|
||||
manifique (1.1.0)
|
||||
faraday (~> 2.9.0)
|
||||
faraday-follow_redirects (= 0.3.0)
|
||||
nokogiri (~> 1.16.0)
|
||||
@ -515,7 +515,7 @@ DEPENDENCIES
|
||||
listen (~> 3.2)
|
||||
lnurl
|
||||
lockbox
|
||||
manifique
|
||||
manifique (~> 1.1.0)
|
||||
net-ldap
|
||||
nostr (~> 0.6.0)
|
||||
pagy (~> 6.0, >= 6.0.2)
|
||||
|
@ -8,8 +8,7 @@ class Services::RemotestorageController < Services::BaseController
|
||||
# unless current_user.service_enabled?(:remotestorage)
|
||||
# redirect_to service_remotestorage_info_path
|
||||
# end
|
||||
@rs_auths = current_user.remote_storage_authorizations
|
||||
# TODO sort by app name
|
||||
# @rs_apps_connected = current_user.remote_storage_authorizations.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -3,7 +3,12 @@ class Services::RsAuthsController < Services::BaseController
|
||||
before_action :require_feature_enabled
|
||||
before_action :require_service_available
|
||||
# before_action :require_service_enabled
|
||||
before_action :find_rs_auth
|
||||
before_action :find_rs_auth, only: [:destroy, :launch_app]
|
||||
|
||||
def index
|
||||
@rs_auths = current_user.remote_storage_authorizations
|
||||
# TODO sort by app name?
|
||||
end
|
||||
|
||||
def destroy
|
||||
@auth.destroy!
|
||||
|
@ -4,7 +4,7 @@ class WellKnownController < ApplicationController
|
||||
def nostr
|
||||
http_status :unprocessable_entity and return if params[:name].blank?
|
||||
domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain
|
||||
relay_url = Setting.nostr_relay_url
|
||||
relay_url = Setting.nostr_relay_url.presence
|
||||
|
||||
if params[:name] == "_"
|
||||
# pubkey for the primary domain without a username (e.g. kosmos.org)
|
||||
|
@ -43,7 +43,7 @@
|
||||
</p>
|
||||
<p>
|
||||
We have run two 6-month trials so far, with the next trial period
|
||||
starting sometime in Q2 2024. Watch your email for notifications about it!
|
||||
starting sometime soon. Watch your email for notifications about it!
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
|
@ -41,15 +41,16 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<% if Setting.remotestorage_enabled? &&
|
||||
Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:80%] bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
||||
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_remotestorage.svg)]">
|
||||
<%= link_to services_storage_path,
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<h3 class="mb-3.5">Storage</h3>
|
||||
<p class="text-gray-600">
|
||||
Community forums and support/help site
|
||||
Sync your data between apps and devices
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -67,16 +68,15 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled? &&
|
||||
Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_remotestorage.svg)]">
|
||||
<%= link_to services_storage_path,
|
||||
bg-[length:80%] 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">Storage</h3>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<p class="text-gray-600">
|
||||
Sync your data between apps and devices
|
||||
Community forums and support/help site
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -2,15 +2,143 @@
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<h3 class="mb-10">Connected Apps</h3>
|
||||
<% if @rs_auths.any? %>
|
||||
<div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12">
|
||||
<% @rs_auths.each do |auth| %>
|
||||
<%= render RsAuthComponent.new(auth: auth) %>
|
||||
<% end %>
|
||||
<p class="mb-6">
|
||||
Store and synchronize your app data across different devices.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<%= render partial: "shared/tabnav_remotestorage" %>
|
||||
|
||||
<section>
|
||||
<h3>Your Storage Address</h3>
|
||||
<p class="mb-6">
|
||||
In order to connect an app to your storage account, give it your 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>
|
||||
</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>Productivity</option>
|
||||
<option>Bookmarks</option>
|
||||
<option>Reading</option>
|
||||
<option>File sharing</option>
|
||||
<option>Learning</option>
|
||||
</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">
|
||||
Productivity
|
||||
</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">
|
||||
Bookmarks
|
||||
</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">
|
||||
Reading
|
||||
</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">
|
||||
File sharing
|
||||
</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">
|
||||
Learning
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Hyperdraft",
|
||||
description: "Create text notes and (optionally) turn them into a website",
|
||||
icon_path: "/img/app_icons/hyperdraft.png",
|
||||
links: [
|
||||
["Web App", "https://hyperdraft.rosano.ca"],
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Notes Together",
|
||||
description: "A powerful note-taking app, with support for attaching images and other files",
|
||||
icon_path: "/img/app_icons/notes-together.png",
|
||||
links: [
|
||||
["Web App", "https://notestogether.hominidsoftware.com"],
|
||||
]
|
||||
) %>
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Papiers",
|
||||
description: "A simple note-taking app",
|
||||
icon_path: "/img/app_icons/papiers.png",
|
||||
links: [
|
||||
["Web App", "https://papiers.gitlab.io"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Webmarks",
|
||||
description: "Archive your bookmarks in your remote storage",
|
||||
icon_path: "/img/app_icons/webmarks.png",
|
||||
links: [
|
||||
["Web App", "https://webmarks.5apps.com"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Pétrolette",
|
||||
description: "A news aggregator that syncs with your remote storage",
|
||||
icon_path: "/img/app_icons/petrolette.png",
|
||||
links: [
|
||||
["Web App", "https://petrolette.space"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Sharesome",
|
||||
description: "Quickly and easily share files from your remote storage",
|
||||
icon_path: "/img/app_icons/sharesome.png",
|
||||
links: [
|
||||
["Web App", "https://sharesome.5apps.com"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||
<%= render AppInfoComponent.new(
|
||||
name: "Kommit",
|
||||
description: "Create flashcards and learn them with spaced-repetition",
|
||||
icon_path: "/img/app_icons/kommit.png",
|
||||
links: [
|
||||
["Web App", "https://kommit.rosano.ca"],
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<p>No apps connected yet.</p>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
|
33
app/views/services/rs_auths/index.html.erb
Normal file
@ -0,0 +1,33 @@
|
||||
<%= render HeaderComponent.new(title: "Storage") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<p class="mb-6">
|
||||
Store and synchronize your app data across different devices.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<%= render partial: "shared/tabnav_remotestorage" %>
|
||||
|
||||
<section>
|
||||
<% if @rs_auths.any? %>
|
||||
<div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12 mt-4">
|
||||
<% @rs_auths.each do |auth| %>
|
||||
<%= render RsAuthComponent.new(auth: auth) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center">
|
||||
<p class="mt-4 mb-12 inline-flex align-center items-center">
|
||||
<%= image_tag("/img/illustrations/undraw_friends_r511.svg", class: 'h-48') %>
|
||||
</p>
|
||||
<h3>
|
||||
No apps connected
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
When connected, your apps will show up here.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
14
app/views/shared/_tabnav_remotestorage.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<section>
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Info", path: services_storage_path,
|
||||
active: current_page?(services_storage_path)
|
||||
) %>
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Connected Apps", path: apps_services_storage_path,
|
||||
active: current_page?(apps_services_storage_path)
|
||||
) %>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
@ -48,7 +48,8 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resource :storage, controller: 'remotestorage', only: [:show] do
|
||||
resources :rs_auths, only: [:destroy] do
|
||||
get :apps, to: "rs_auths#index"
|
||||
resources :rs_auths, only: [:index, :destroy] do
|
||||
member do
|
||||
get :revoke, to: 'rs_auths#destroy'
|
||||
get :launch_app
|
||||
|
BIN
public/img/app_icons/hyperdraft.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public/img/app_icons/kommit.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/img/app_icons/notes-together.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/img/app_icons/papiers.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/img/app_icons/petrolette.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/app_icons/sharesome.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/img/app_icons/webmarks.png
Normal file
After Width: | Height: | Size: 20 KiB |
1
public/img/illustrations/undraw_friends_r511.svg
Normal file
After Width: | Height: | Size: 11 KiB |
@ -47,6 +47,10 @@ RSpec.describe "Well-known URLs", type: :request do
|
||||
end
|
||||
|
||||
context "without relay configured" do
|
||||
before do
|
||||
Setting.nostr_relay_url = ""
|
||||
end
|
||||
|
||||
it "does not include a recommended relay" do
|
||||
get "/.well-known/nostr.json?name=bobdylan"
|
||||
res = JSON.parse(response.body)
|
||||
|
@ -4,6 +4,10 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do
|
||||
let(:user) { create :user, ln_account: "123456abcdef" }
|
||||
let(:zap) { create :zap, user: user }
|
||||
|
||||
before do
|
||||
Setting.nostr_relay_url = ""
|
||||
end
|
||||
|
||||
describe "Default/delayed execution" do
|
||||
it "publishes zap receipts to all requested relays" do
|
||||
expect(NostrPublishEventJob).to receive(:perform_later)
|
||||
|