rails_api_keys-ui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE +21 -0
- data/README.md +130 -0
- data/app/assets/stylesheets/rails_api_keys/ui.css +138 -0
- data/app/controllers/rails_api_keys/ui/api_keys_controller.rb +152 -0
- data/app/controllers/rails_api_keys/ui/application_controller.rb +10 -0
- data/app/helpers/rails_api_keys/ui/api_keys_helper.rb +43 -0
- data/app/javascript/rails_api_keys/ui/controllers/clipboard_controller.js +19 -0
- data/app/javascript/rails_api_keys/ui/controllers/modal_controller.js +43 -0
- data/app/policies/rails_api_keys/ui/api_key_policy.rb +49 -0
- data/app/views/rails_api_keys/ui/api_keys/_form_modal.html.erb +49 -0
- data/app/views/rails_api_keys/ui/api_keys/_list.html.erb +28 -0
- data/app/views/rails_api_keys/ui/api_keys/_panel.html.erb +27 -0
- data/app/views/rails_api_keys/ui/api_keys/_reveal_modal.html.erb +48 -0
- data/app/views/rails_api_keys/ui/api_keys/index.html.erb +5 -0
- data/config/locales/en.yml +42 -0
- data/config/routes.rb +4 -0
- data/lib/generators/rails_api_keys_ui/controllers/controllers_generator.rb +17 -0
- data/lib/generators/rails_api_keys_ui/controllers/templates/api_keys_controller.rb.tt +20 -0
- data/lib/generators/rails_api_keys_ui/install/install_generator.rb +52 -0
- data/lib/generators/rails_api_keys_ui/install/templates/initializer.rb.tt +10 -0
- data/lib/generators/rails_api_keys_ui/install/templates/javascript/clipboard_controller.js +19 -0
- data/lib/generators/rails_api_keys_ui/install/templates/javascript/modal_controller.js +43 -0
- data/lib/generators/rails_api_keys_ui/locales/locales_generator.rb +17 -0
- data/lib/generators/rails_api_keys_ui/views/views_generator.rb +17 -0
- data/lib/rails_api_keys/ui/configuration.rb +24 -0
- data/lib/rails_api_keys/ui/engine.rb +39 -0
- data/lib/rails_api_keys/ui/mount_registry.rb +28 -0
- data/lib/rails_api_keys/ui/route_mapper.rb +55 -0
- data/lib/rails_api_keys/ui/version.rb +7 -0
- data/lib/rails_api_keys/ui.rb +39 -0
- data/lib/rails_api_keys_ui.rb +3 -0
- metadata +127 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div data-modal-target="modal"
|
|
2
|
+
data-action="keydown@window->modal#closeOnEscape"
|
|
3
|
+
class="hidden fixed inset-0 z-50 overflow-y-auto">
|
|
4
|
+
<div class="flex items-center justify-center min-h-screen px-4">
|
|
5
|
+
<div data-modal-target="backdrop"
|
|
6
|
+
data-action="click->modal#closeOnBackdropClick"
|
|
7
|
+
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
|
|
8
|
+
<div data-modal-target="panel"
|
|
9
|
+
class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6 z-10">
|
|
10
|
+
<div class="flex justify-between items-center mb-4">
|
|
11
|
+
<h3 class="text-lg font-medium text-gray-900"><%= title %></h3>
|
|
12
|
+
<button type="button"
|
|
13
|
+
data-action="click->modal#close"
|
|
14
|
+
class="text-gray-400 hover:text-gray-500">
|
|
15
|
+
×
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
<%= form_with url: create_path, scope: :api_key, class: "space-y-4" do |f| %>
|
|
19
|
+
<div>
|
|
20
|
+
<%= f.label :name, t("rails_api_keys_ui.api_keys.form.name"), class: "block text-sm font-medium text-gray-700 mb-2" %>
|
|
21
|
+
<%= f.text_field :name, required: true, class: "form-input w-full border-gray-300 rounded-md shadow-sm", placeholder: t("rails_api_keys_ui.api_keys.form.name_placeholder") %>
|
|
22
|
+
</div>
|
|
23
|
+
<div>
|
|
24
|
+
<%= f.label :permission, t("rails_api_keys_ui.api_keys.form.permission"), class: "block text-sm font-medium text-gray-700 mb-2" %>
|
|
25
|
+
<%= f.select :permission,
|
|
26
|
+
options_for_select(
|
|
27
|
+
[
|
|
28
|
+
[ t("rails_api_keys_ui.api_keys.form.permission_read"), "read" ],
|
|
29
|
+
[ t("rails_api_keys_ui.api_keys.form.permission_read_write"), "read_write" ]
|
|
30
|
+
],
|
|
31
|
+
"read"
|
|
32
|
+
),
|
|
33
|
+
{},
|
|
34
|
+
required: true,
|
|
35
|
+
class: "form-input w-full border-gray-300 rounded-md shadow-sm" %>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="flex justify-end space-x-3 pt-4">
|
|
38
|
+
<button type="button"
|
|
39
|
+
data-action="click->modal#close"
|
|
40
|
+
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
|
|
41
|
+
<%= t("rails_api_keys_ui.api_keys.cancel") %>
|
|
42
|
+
</button>
|
|
43
|
+
<%= f.submit t("rails_api_keys_ui.api_keys.form.submit"),
|
|
44
|
+
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 cursor-pointer" %>
|
|
45
|
+
</div>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<% if api_keys.any? %>
|
|
2
|
+
<div class="space-y-3">
|
|
3
|
+
<% api_keys.each do |api_key| %>
|
|
4
|
+
<div class="flex flex-col gap-3 p-4 border border-gray-200 rounded-lg sm:flex-row sm:items-center sm:justify-between">
|
|
5
|
+
<div>
|
|
6
|
+
<p class="text-sm font-medium text-gray-900"><%= api_key.name %></p>
|
|
7
|
+
<p class="mt-1 text-sm text-gray-500 font-mono"><%= api_key.token_display_prefix %>…</p>
|
|
8
|
+
<p class="mt-1 text-xs text-gray-400">
|
|
9
|
+
<%= t("rails_api_keys_ui.api_keys.permission_#{api_key.permission}") %>
|
|
10
|
+
·
|
|
11
|
+
<% if api_key.last_used_at.present? %>
|
|
12
|
+
<%= t("rails_api_keys_ui.api_keys.last_used", time: l(api_key.last_used_at, format: :short)) %>
|
|
13
|
+
<% else %>
|
|
14
|
+
<%= t("rails_api_keys_ui.api_keys.never_used") %>
|
|
15
|
+
<% end %>
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
<%= button_to t("rails_api_keys_ui.api_keys.revoke"),
|
|
19
|
+
destroy_path.respond_to?(:call) ? destroy_path.call(api_key) : destroy_path,
|
|
20
|
+
method: :delete,
|
|
21
|
+
class: "inline-flex items-center px-3 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-white hover:bg-red-50",
|
|
22
|
+
data: { turbo_confirm: t("rails_api_keys_ui.api_keys.revoke_confirm") } %>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
<% else %>
|
|
27
|
+
<p class="text-sm text-gray-500"><%= t("rails_api_keys_ui.api_keys.empty") %></p>
|
|
28
|
+
<% end %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%= rails_api_keys_ui_stylesheet %>
|
|
2
|
+
<div class="rails-api-keys-ui">
|
|
3
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
4
|
+
<div class="mb-6 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
5
|
+
<div>
|
|
6
|
+
<h2 class="text-lg font-medium text-gray-900"><%= t("rails_api_keys_ui.api_keys.title") %></h2>
|
|
7
|
+
<p class="mt-1 text-sm text-gray-500"><%= t("rails_api_keys_ui.api_keys.description") %></p>
|
|
8
|
+
</div>
|
|
9
|
+
<div data-controller="modal">
|
|
10
|
+
<button type="button"
|
|
11
|
+
data-action="click->modal#open"
|
|
12
|
+
class="inline-flex items-center px-3 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700">
|
|
13
|
+
<%= t("rails_api_keys_ui.api_keys.new") %>
|
|
14
|
+
</button>
|
|
15
|
+
<%= render "rails_api_keys/ui/api_keys/form_modal", create_path: create_path, title: t("rails_api_keys_ui.api_keys.new") %>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<%= render "rails_api_keys/ui/api_keys/list", api_keys: api_keys, destroy_path: destroy_path %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<% if raw_token.present? %>
|
|
23
|
+
<div data-controller="modal" data-modal-open-on-connect-value="true">
|
|
24
|
+
<%= render "rails_api_keys/ui/api_keys/reveal_modal", raw_token: raw_token %>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<div data-modal-target="modal"
|
|
2
|
+
data-action="keydown@window->modal#closeOnEscape"
|
|
3
|
+
class="fixed inset-0 z-50 overflow-y-auto">
|
|
4
|
+
<div class="flex items-center justify-center min-h-screen px-4">
|
|
5
|
+
<div data-modal-target="backdrop"
|
|
6
|
+
data-action="click->modal#closeOnBackdropClick"
|
|
7
|
+
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
|
|
8
|
+
<div data-modal-target="panel"
|
|
9
|
+
class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6 z-10">
|
|
10
|
+
<div class="flex justify-between items-center mb-4">
|
|
11
|
+
<h3 class="text-lg font-medium text-gray-900"><%= t("rails_api_keys_ui.api_keys.reveal.title") %></h3>
|
|
12
|
+
<button type="button"
|
|
13
|
+
data-action="click->modal#close"
|
|
14
|
+
class="text-gray-400 hover:text-gray-500">
|
|
15
|
+
×
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
<p class="text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded-md p-3 mb-4">
|
|
19
|
+
<%= t("rails_api_keys_ui.api_keys.reveal.warning") %>
|
|
20
|
+
</p>
|
|
21
|
+
<div data-controller="clipboard" class="space-y-3">
|
|
22
|
+
<label class="block text-sm font-medium text-gray-700"><%= t("rails_api_keys_ui.api_keys.reveal.token") %></label>
|
|
23
|
+
<div class="flex gap-2">
|
|
24
|
+
<input type="text"
|
|
25
|
+
readonly
|
|
26
|
+
data-clipboard-target="source"
|
|
27
|
+
value="<%= raw_token %>"
|
|
28
|
+
class="form-input font-mono text-sm flex-1 border-gray-300 rounded-md shadow-sm">
|
|
29
|
+
<button type="button"
|
|
30
|
+
data-action="click->clipboard#copy"
|
|
31
|
+
class="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
|
|
32
|
+
<%= t("rails_api_keys_ui.api_keys.reveal.copy") %>
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
<p data-clipboard-target="feedback" class="hidden text-sm text-primary-600">
|
|
36
|
+
<%= t("rails_api_keys_ui.api_keys.reveal.copied") %>
|
|
37
|
+
</p>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex justify-end pt-6">
|
|
40
|
+
<button type="button"
|
|
41
|
+
data-action="click->modal#close"
|
|
42
|
+
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700">
|
|
43
|
+
<%= t("rails_api_keys_ui.api_keys.reveal.done") %>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
en:
|
|
2
|
+
rails_api_keys_ui:
|
|
3
|
+
api_keys:
|
|
4
|
+
title: "API keys"
|
|
5
|
+
description: "Create personal API keys for integrations."
|
|
6
|
+
new: "Create API key"
|
|
7
|
+
empty: "No API keys yet."
|
|
8
|
+
revoke: "Revoke"
|
|
9
|
+
revoke_confirm: "Revoke this API key? Integrations using it will stop working."
|
|
10
|
+
never_used: "Never used"
|
|
11
|
+
last_used: "Last used %{time}"
|
|
12
|
+
permission_read: "Read"
|
|
13
|
+
permission_read_write: "Read & write"
|
|
14
|
+
cancel: "Cancel"
|
|
15
|
+
form:
|
|
16
|
+
name: "Name"
|
|
17
|
+
name_placeholder: "e.g. Zapier production"
|
|
18
|
+
permission: "Permission"
|
|
19
|
+
permission_read: "Read"
|
|
20
|
+
permission_read_write: "Read & write"
|
|
21
|
+
submit: "Create API key"
|
|
22
|
+
reveal:
|
|
23
|
+
title: "Your new API key"
|
|
24
|
+
warning: "Copy this key now. You will not be able to see it again."
|
|
25
|
+
token: "API key"
|
|
26
|
+
copy: "Copy"
|
|
27
|
+
copied: "Copied to clipboard"
|
|
28
|
+
done: "Done"
|
|
29
|
+
# Lazy lookup scopes for engine controller (`t(".created")` / `t(".revoked")`)
|
|
30
|
+
rails_api_keys:
|
|
31
|
+
ui:
|
|
32
|
+
api_keys:
|
|
33
|
+
create:
|
|
34
|
+
created: "API key created. Copy it now — it won’t be shown again."
|
|
35
|
+
destroy:
|
|
36
|
+
revoked: "API key revoked."
|
|
37
|
+
# Lazy lookup when host subclasses as ApiKeysController
|
|
38
|
+
api_keys:
|
|
39
|
+
create:
|
|
40
|
+
created: "API key created. Copy it now — it won’t be shown again."
|
|
41
|
+
destroy:
|
|
42
|
+
revoked: "API key revoked."
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeysUi
|
|
6
|
+
module Generators
|
|
7
|
+
class ControllersGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Copy rails_api_keys-ui controller into the host app"
|
|
11
|
+
|
|
12
|
+
def copy_controller
|
|
13
|
+
template "api_keys_controller.rb.tt", "app/controllers/api_keys_controller.rb"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ApiKeysController < RailsApiKeys::UI::ApiKeysController
|
|
4
|
+
# Customize authorization or owner resolution here.
|
|
5
|
+
#
|
|
6
|
+
# Example (nested under employees):
|
|
7
|
+
#
|
|
8
|
+
# before_action :set_employee
|
|
9
|
+
# before_action :authorize_employee_api_keys!
|
|
10
|
+
#
|
|
11
|
+
# private
|
|
12
|
+
#
|
|
13
|
+
# def set_employee
|
|
14
|
+
# @employee = Employee.find(params[:employee_id])
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# def authorize_employee_api_keys!
|
|
18
|
+
# authorize @employee, :show_api_keys?
|
|
19
|
+
# end
|
|
20
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeysUi
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Install rails_api_keys-ui (initializer + optional Stimulus controllers)"
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "initializer.rb.tt", "config/initializers/rails_api_keys_ui.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy_stimulus_controllers
|
|
17
|
+
return unless yes?("Copy example Stimulus modal/clipboard controllers into the host app?")
|
|
18
|
+
|
|
19
|
+
copy_file "javascript/modal_controller.js", "app/javascript/controllers/modal_controller.js"
|
|
20
|
+
copy_file "javascript/clipboard_controller.js", "app/javascript/controllers/clipboard_controller.js"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def show_readme
|
|
24
|
+
say <<~MSG
|
|
25
|
+
|
|
26
|
+
rails_api_keys-ui installed.
|
|
27
|
+
|
|
28
|
+
1. Draw routes, for example:
|
|
29
|
+
|
|
30
|
+
resources :employees do
|
|
31
|
+
rails_api_keys_ui owner: :current_user, controller: "api_keys"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
rails_api_keys_ui owner: :company, path: "company/api_keys"
|
|
35
|
+
|
|
36
|
+
2. Views expect Stimulus controllers registered as `modal` and `clipboard`
|
|
37
|
+
(open/close + openOnConnect; copy from source target). Skip the copy
|
|
38
|
+
step above if your app already has compatible controllers.
|
|
39
|
+
|
|
40
|
+
3. The panel inlines a scoped stylesheet by default. Override brand colors
|
|
41
|
+
with --rak-ui-primary-600 / --rak-ui-primary-700 on .rails-api-keys-ui,
|
|
42
|
+
or set config.include_stylesheet = false if you style it yourself.
|
|
43
|
+
|
|
44
|
+
4. Optional: rails generate rails_api_keys_ui:controllers
|
|
45
|
+
rails generate rails_api_keys_ui:views
|
|
46
|
+
rails generate rails_api_keys_ui:locales
|
|
47
|
+
|
|
48
|
+
MSG
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# RailsApiKeys::UI.configure do |config|
|
|
4
|
+
# # config.parent_controller = "ApplicationController"
|
|
5
|
+
# # config.current_owner = ->(controller) { controller.current_user }
|
|
6
|
+
# # config.policy_class = "RailsApiKeys::UI::ApiKeyPolicy"
|
|
7
|
+
# # config.raw_token_flash_key = :rails_api_keys_raw_token
|
|
8
|
+
# # config.include_stylesheet = true # false if the host already styles the panel
|
|
9
|
+
# # config.authorize = ->(controller, action, record) { ... }
|
|
10
|
+
# end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["source", "feedback"]
|
|
5
|
+
|
|
6
|
+
async copy() {
|
|
7
|
+
const value = this.sourceTarget.value
|
|
8
|
+
try {
|
|
9
|
+
await navigator.clipboard.writeText(value)
|
|
10
|
+
} catch (_error) {
|
|
11
|
+
this.sourceTarget.select()
|
|
12
|
+
document.execCommand("copy")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (this.hasFeedbackTarget) {
|
|
16
|
+
this.feedbackTarget.classList.remove("hidden")
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["modal", "backdrop", "panel"]
|
|
5
|
+
static values = { openOnConnect: Boolean }
|
|
6
|
+
|
|
7
|
+
connect() {
|
|
8
|
+
if (this.openOnConnectValue) {
|
|
9
|
+
this.open()
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (this.hasModalTarget) {
|
|
14
|
+
this.modalTarget.classList.add("hidden")
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
open() {
|
|
19
|
+
if (this.hasModalTarget) {
|
|
20
|
+
this.modalTarget.classList.remove("hidden")
|
|
21
|
+
document.body.classList.add("overflow-hidden")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
close() {
|
|
26
|
+
if (this.hasModalTarget) {
|
|
27
|
+
this.modalTarget.classList.add("hidden")
|
|
28
|
+
document.body.classList.remove("overflow-hidden")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
closeOnBackdropClick(event) {
|
|
33
|
+
if (event.target === this.backdropTarget) {
|
|
34
|
+
this.close()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
closeOnEscape(event) {
|
|
39
|
+
if (event.key === "Escape") {
|
|
40
|
+
this.close()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeysUi
|
|
6
|
+
module Generators
|
|
7
|
+
class LocalesGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("../../../../../config/locales", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Copy rails_api_keys-ui locale files into the host app"
|
|
11
|
+
|
|
12
|
+
def copy_locales
|
|
13
|
+
copy_file "en.yml", "config/locales/rails_api_keys_ui.en.yml"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeysUi
|
|
6
|
+
module Generators
|
|
7
|
+
class ViewsGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("../../../../../app/views/rails_api_keys/ui", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Copy rails_api_keys-ui views into the host app for editing"
|
|
11
|
+
|
|
12
|
+
def copy_views
|
|
13
|
+
directory "api_keys", "app/views/rails_api_keys/ui/api_keys"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsApiKeys
|
|
4
|
+
module UI
|
|
5
|
+
# Host overrides. Set via {RailsApiKeys::UI.configure}.
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :parent_controller,
|
|
8
|
+
:current_owner,
|
|
9
|
+
:authorize,
|
|
10
|
+
:policy_class,
|
|
11
|
+
:raw_token_flash_key,
|
|
12
|
+
:include_stylesheet
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@parent_controller = "ApplicationController"
|
|
16
|
+
@current_owner = ->(controller) { controller.current_user }
|
|
17
|
+
@authorize = nil
|
|
18
|
+
@policy_class = "RailsApiKeys::UI::ApiKeyPolicy"
|
|
19
|
+
@raw_token_flash_key = :rails_api_keys_raw_token
|
|
20
|
+
@include_stylesheet = true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_api_keys/ui/route_mapper"
|
|
4
|
+
|
|
5
|
+
module RailsApiKeys
|
|
6
|
+
module UI
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
isolate_namespace RailsApiKeys::UI
|
|
9
|
+
|
|
10
|
+
initializer "rails_api_keys_ui.inflections", before: :set_autoload_paths do
|
|
11
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
12
|
+
inflect.acronym "UI"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "rails_api_keys_ui.routes", before: :add_routing_paths do
|
|
17
|
+
ActionDispatch::Routing::Mapper.include(RailsApiKeys::UI::RouteMapper)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
initializer "rails_api_keys_ui.helpers" do
|
|
21
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
22
|
+
helper_path = RailsApiKeys::UI::Engine.root.join("app/helpers/rails_api_keys/ui/api_keys_helper.rb")
|
|
23
|
+
require helper_path.to_s unless defined?(RailsApiKeys::UI::ApiKeysHelper)
|
|
24
|
+
helper RailsApiKeys::UI::ApiKeysHelper
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
initializer "rails_api_keys_ui.assets" do |app|
|
|
29
|
+
next unless app.config.respond_to?(:assets)
|
|
30
|
+
|
|
31
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
32
|
+
app.config.assets.paths << root.join("app/assets/stylesheets")
|
|
33
|
+
if app.config.assets.respond_to?(:precompile)
|
|
34
|
+
app.config.assets.precompile << "rails_api_keys/ui.css"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsApiKeys
|
|
4
|
+
module UI
|
|
5
|
+
# Stores per-draw-helper options (owner resolution, authorize, etc.).
|
|
6
|
+
# Route defaults only carry the mount name; procs live here.
|
|
7
|
+
class MountRegistry
|
|
8
|
+
def initialize
|
|
9
|
+
@mounts = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def register(name, options)
|
|
13
|
+
@mounts[name.to_s] = options.dup
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fetch(name)
|
|
17
|
+
@mounts.fetch(name.to_s) do
|
|
18
|
+
raise KeyError, "Unknown rails_api_keys_ui mount #{name.inspect}. " \
|
|
19
|
+
"Did routes reload without re-registering?"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def clear!
|
|
24
|
+
@mounts.clear
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsApiKeys
|
|
4
|
+
module UI
|
|
5
|
+
# Adds +rails_api_keys_ui+ to the host routes DSL.
|
|
6
|
+
module RouteMapper
|
|
7
|
+
# Draws +index+/+create+/+destroy+ API key routes for one owner mount.
|
|
8
|
+
#
|
|
9
|
+
# @param options [Hash]
|
|
10
|
+
# @option options [Symbol, String, Proc] :owner key owner resolution
|
|
11
|
+
# (:current_user, :company, or callable)
|
|
12
|
+
# @option options [Proc] :authorize optional per-mount authorize override
|
|
13
|
+
# @option options [String] :controller host or engine controller
|
|
14
|
+
# @option options [String] :path URL path segment (default "api_keys")
|
|
15
|
+
# @option options [String, Symbol] :name mount registry key
|
|
16
|
+
def rails_api_keys_ui(options = {})
|
|
17
|
+
opts = options.dup
|
|
18
|
+
mount_name = (opts.delete(:name) || default_rails_api_keys_ui_mount_name).to_s
|
|
19
|
+
owner = opts.key?(:owner) ? opts.delete(:owner) : :current_user
|
|
20
|
+
authorize = opts.delete(:authorize)
|
|
21
|
+
controller = opts.delete(:controller) || "rails_api_keys/ui/api_keys"
|
|
22
|
+
path = opts.delete(:path) || "api_keys"
|
|
23
|
+
as = opts.delete(:as)
|
|
24
|
+
|
|
25
|
+
RailsApiKeys::UI.mounts.register(
|
|
26
|
+
mount_name,
|
|
27
|
+
owner: owner,
|
|
28
|
+
authorize: authorize
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
resource_options = {
|
|
32
|
+
only: %i[index create destroy],
|
|
33
|
+
controller: controller,
|
|
34
|
+
path: path,
|
|
35
|
+
defaults: { rails_api_keys_ui_mount: mount_name }
|
|
36
|
+
}
|
|
37
|
+
resource_options[:as] = as if as
|
|
38
|
+
|
|
39
|
+
resources :api_keys, **resource_options, **opts
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def default_rails_api_keys_ui_mount_name
|
|
45
|
+
scope = @scope
|
|
46
|
+
parts = []
|
|
47
|
+
parts << scope[:module] if scope[:module]
|
|
48
|
+
parts << scope[:as] if scope[:as]
|
|
49
|
+
parts << scope[:path] if scope[:path].present? && scope[:path] != "/"
|
|
50
|
+
parts << "api_keys"
|
|
51
|
+
parts.compact.join("_").gsub(%r{[/\-]}, "_").gsub(/__+/, "_")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_api_keys"
|
|
4
|
+
require "rails_api_keys/ui/version"
|
|
5
|
+
require "rails_api_keys/ui/configuration"
|
|
6
|
+
require "rails_api_keys/ui/mount_registry"
|
|
7
|
+
|
|
8
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
9
|
+
inflect.acronym "UI"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rails_api_keys/ui/engine"
|
|
13
|
+
|
|
14
|
+
module RailsApiKeys
|
|
15
|
+
# HTML/JSON management UI for {RailsApiKeys::ApiKey}.
|
|
16
|
+
module UI
|
|
17
|
+
class << self
|
|
18
|
+
def configuration
|
|
19
|
+
@configuration ||= Configuration.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configure
|
|
23
|
+
yield configuration
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reset_configuration!
|
|
27
|
+
@configuration = Configuration.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def mounts
|
|
31
|
+
@mounts ||= MountRegistry.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset_mounts!
|
|
35
|
+
mounts.clear!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|