js_admin 0.1.0 → 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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +53 -5
- data/app/controllers/js_admin/application_controller.rb +20 -4
- data/app/controllers/js_admin/resources_controller.rb +5 -3
- data/app/helpers/js_admin/application_helper.rb +8 -2
- data/app/views/js_admin/models/_index_content.html.erb +2 -2
- data/app/views/js_admin/models/index.html.erb +1 -1
- data/app/views/js_admin/resources/_edit_content.html.erb +3 -3
- data/app/views/js_admin/resources/_form.html.erb +3 -2
- data/app/views/js_admin/resources/_index_content.html.erb +18 -18
- data/app/views/js_admin/resources/_new_content.html.erb +3 -3
- data/app/views/js_admin/resources/_show_content.html.erb +3 -3
- data/app/views/js_admin/resources/edit.html.erb +1 -1
- data/app/views/js_admin/resources/new.html.erb +1 -1
- data/app/views/layouts/js_admin/_sidebar.html.erb +3 -3
- data/config/locales/js_admin.en.yml +65 -0
- data/config/locales/js_admin.ko.yml +65 -0
- data/lib/generators/js_admin/install/install_generator.rb +9 -2
- data/lib/js_admin/configuration.rb +67 -1
- data/lib/js_admin/configuration_error.rb +4 -0
- data/lib/js_admin/field.rb +17 -3
- data/lib/js_admin/registry.rb +4 -1
- data/lib/js_admin/resource.rb +5 -1
- data/lib/js_admin/version.rb +1 -1
- data/lib/js_admin.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bacbc059bd6ee6d36d9ad35c5fa86b8cb258b4d1fda939bf3bd98b269f74c7ea
|
|
4
|
+
data.tar.gz: ef27365d7d4d61abfb48a114bcd9a1eb3a5f569b943a2369951f12e4e78aef22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bccfe7a4bc553ff4581f6a31a4beb244fe47a39cec17a3d851b527ff74f8d2b3d8d12aa0e3f71a85ac74b2bbcd4bccddb919327cb8235c4688fa37483cc91973
|
|
7
|
+
data.tar.gz: 79370d4f7c372a9527924c0768f641109424b24c55fb42ef2f16011702f518b04560201c5b3c43c2200c860a60a447f1edf72ce542f33da2d53a3ab2af5a74aa
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.0 - 2026-10-18
|
|
6
|
+
|
|
7
|
+
- Add host application authentication and authorization callbacks as an alternative to Basic Auth.
|
|
8
|
+
- Reject conflicting or incomplete authentication configuration with localized errors.
|
|
9
|
+
- Add English and Korean I18n translations for the JSAdmin interface.
|
|
10
|
+
- Add `select_options` configuration to render a column as a select with static or callable choices.
|
|
11
|
+
|
|
5
12
|
## 0.1.0 - 2026-07-18
|
|
6
13
|
|
|
7
14
|
- Change the default mount path to `/admin`.
|
data/README.md
CHANGED
|
@@ -74,9 +74,12 @@ Available configuration options:
|
|
|
74
74
|
| `records_per_page` | `10` | Number of records shown per resource index page. |
|
|
75
75
|
| `association_limit` | `100` | Maximum number of associated records loaded into a `belongs_to` select. |
|
|
76
76
|
| `display_name_methods` | `%i[name title email username id]` | Methods tried in order when displaying records in tables and select labels. |
|
|
77
|
+
| `field_select_options` | `{}` | Select choices by model and column. Register them with `select_options`. |
|
|
77
78
|
| `basic_auth_username` | `nil` | HTTP Basic Auth username. Auth is disabled unless username and password are both present. |
|
|
78
79
|
| `basic_auth_password` | `nil` | HTTP Basic Auth password. |
|
|
79
80
|
| `basic_auth_realm` | `"JSAdmin"` | HTTP Basic Auth realm. |
|
|
81
|
+
| `authenticate_with` | `nil` | Controller-context callback delegated to the host application's authentication. |
|
|
82
|
+
| `authorize_with` | `nil` | Optional controller-context callback that returns whether the authenticated user may access JSAdmin. |
|
|
80
83
|
|
|
81
84
|
Example:
|
|
82
85
|
|
|
@@ -86,14 +89,21 @@ JSAdmin.configure do |config|
|
|
|
86
89
|
config.include_models User, Post
|
|
87
90
|
config.exclude_models AuditLog
|
|
88
91
|
config.records_per_page = 10
|
|
92
|
+
config.select_options "Post", category: -> { Category.pluck(:name) }
|
|
89
93
|
end
|
|
90
94
|
```
|
|
91
95
|
|
|
96
|
+
`select_options` renders the column as a select. Pass an array or a callable;
|
|
97
|
+
callables are evaluated each time the form renders. Choices can be values or
|
|
98
|
+
`[label, value]` pairs.
|
|
99
|
+
|
|
92
100
|
## Authentication
|
|
93
101
|
|
|
94
|
-
JSAdmin works without authentication unless Basic Auth credentials
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
JSAdmin works without authentication unless Basic Auth credentials or an
|
|
103
|
+
authentication callback are configured. Choose exactly one of the following
|
|
104
|
+
authentication strategies.
|
|
105
|
+
|
|
106
|
+
For HTTP Basic Auth, configure both username and password:
|
|
97
107
|
|
|
98
108
|
```ruby
|
|
99
109
|
# config/initializers/js_admin.rb
|
|
@@ -104,8 +114,45 @@ JSAdmin.configure do |config|
|
|
|
104
114
|
end
|
|
105
115
|
```
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
To delegate authentication to the host application, provide a callback instead.
|
|
118
|
+
JSAdmin controllers inherit from the host application's `ApplicationController`,
|
|
119
|
+
and the callback runs in the controller context:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# config/initializers/js_admin.rb
|
|
123
|
+
JSAdmin.configure do |config|
|
|
124
|
+
config.authenticate_with do
|
|
125
|
+
authenticate_user!
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
config.authorize_with do
|
|
129
|
+
current_user&.admin?
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`authorize_with` is optional. When configured, returning `false` responds with
|
|
135
|
+
`403 Forbidden`. Basic Auth and `authenticate_with` cannot be configured
|
|
136
|
+
together. Partial Basic Auth credentials and `authorize_with` without
|
|
137
|
+
`authenticate_with` also raise `JSAdmin::ConfigurationError`.
|
|
138
|
+
|
|
139
|
+
For production apps, you can alternatively mount JSAdmin inside the host
|
|
140
|
+
application's admin authentication constraint.
|
|
141
|
+
|
|
142
|
+
## Internationalization
|
|
143
|
+
|
|
144
|
+
JSAdmin follows the host application's `I18n.locale`.
|
|
145
|
+
|
|
146
|
+
Supported locales:
|
|
147
|
+
|
|
148
|
+
| Locale | Language |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `en` | English |
|
|
151
|
+
| `ko` | 한국어 |
|
|
152
|
+
|
|
153
|
+
The translations cover the interface, CRUD notices, configuration errors, and
|
|
154
|
+
generator messages. Host applications can override any key under the
|
|
155
|
+
`js_admin` namespace.
|
|
109
156
|
|
|
110
157
|
## Field Mapping
|
|
111
158
|
|
|
@@ -117,6 +164,7 @@ JSAdmin maps database columns to inputs automatically:
|
|
|
117
164
|
| `boolean` | checkbox |
|
|
118
165
|
| `integer`, `bigint`, `float`, `decimal` | number input |
|
|
119
166
|
| `date`, `datetime`, `time` | matching date/time input |
|
|
167
|
+
| `select_options` configured column | select |
|
|
120
168
|
| Rails enum | select |
|
|
121
169
|
| `belongs_to` foreign key | select |
|
|
122
170
|
| other columns | text input |
|
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
require "digest"
|
|
2
2
|
|
|
3
3
|
module JSAdmin
|
|
4
|
-
class ApplicationController <
|
|
5
|
-
before_action :
|
|
4
|
+
class ApplicationController < ::ApplicationController
|
|
5
|
+
before_action :authenticate_js_admin
|
|
6
|
+
before_action :authorize_js_admin
|
|
6
7
|
|
|
7
8
|
private
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
-
|
|
10
|
+
def authenticate_js_admin
|
|
11
|
+
case JSAdmin.configuration.authentication_strategy
|
|
12
|
+
when :basic_auth
|
|
13
|
+
authenticate_basic_auth
|
|
14
|
+
when :callback
|
|
15
|
+
instance_exec(&JSAdmin.configuration.authentication_callback)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
11
18
|
|
|
19
|
+
def authenticate_basic_auth
|
|
12
20
|
authenticate_or_request_with_http_basic(basic_auth_realm) do |username, password|
|
|
13
21
|
secure_compare(username, JSAdmin.configuration.basic_auth_username) &&
|
|
14
22
|
secure_compare(password, JSAdmin.configuration.basic_auth_password)
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
|
|
26
|
+
def authorize_js_admin
|
|
27
|
+
callback = JSAdmin.configuration.authorization_callback
|
|
28
|
+
return unless callback
|
|
29
|
+
return if instance_exec(&callback)
|
|
30
|
+
|
|
31
|
+
head :forbidden
|
|
32
|
+
end
|
|
33
|
+
|
|
18
34
|
def basic_auth_realm
|
|
19
35
|
realm = JSAdmin.configuration.basic_auth_realm.to_s
|
|
20
36
|
return "JSAdmin" if realm.empty?
|
|
@@ -18,7 +18,8 @@ module JSAdmin
|
|
|
18
18
|
@record = @resource.new_record(resource_params)
|
|
19
19
|
|
|
20
20
|
if @record.save
|
|
21
|
-
redirect_to resource_path(@resource.model_id, @record),
|
|
21
|
+
redirect_to resource_path(@resource.model_id, @record),
|
|
22
|
+
notice: t("js_admin.resources.notices.created", resource: @resource.singular_label)
|
|
22
23
|
else
|
|
23
24
|
render :new, status: :unprocessable_entity
|
|
24
25
|
end
|
|
@@ -29,7 +30,8 @@ module JSAdmin
|
|
|
29
30
|
|
|
30
31
|
def update
|
|
31
32
|
if @record.update(resource_params)
|
|
32
|
-
redirect_to resource_path(@resource.model_id, @record),
|
|
33
|
+
redirect_to resource_path(@resource.model_id, @record),
|
|
34
|
+
notice: t("js_admin.resources.notices.updated", resource: @resource.singular_label)
|
|
33
35
|
else
|
|
34
36
|
render :edit, status: :unprocessable_entity
|
|
35
37
|
end
|
|
@@ -37,7 +39,7 @@ module JSAdmin
|
|
|
37
39
|
|
|
38
40
|
def destroy
|
|
39
41
|
@record.destroy
|
|
40
|
-
message = "
|
|
42
|
+
message = t("js_admin.resources.notices.deleted", resource: @resource.singular_label)
|
|
41
43
|
|
|
42
44
|
respond_to do |format|
|
|
43
45
|
format.turbo_stream do
|
|
@@ -101,6 +101,8 @@ module JSAdmin
|
|
|
101
101
|
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "datetime-local", value: js_admin_formatted_field_value(form, field, "%Y-%m-%dT%H:%M:%S")))
|
|
102
102
|
when :time_field
|
|
103
103
|
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "time", value: js_admin_formatted_field_value(form, field, "%H:%M:%S")))
|
|
104
|
+
when :configured_select
|
|
105
|
+
select_tag(js_admin_field_name(form, field), options_for_select(field.configured_select_values, js_admin_field_value(form, field)), js_admin_select_options(form, field))
|
|
104
106
|
when :enum_select
|
|
105
107
|
select_tag(js_admin_field_name(form, field), options_for_select(field.enum_values, js_admin_field_value(form, field)), js_admin_select_options(form, field))
|
|
106
108
|
when :association_select
|
|
@@ -171,7 +173,11 @@ module JSAdmin
|
|
|
171
173
|
|
|
172
174
|
def js_admin_table_value(field, record)
|
|
173
175
|
value = field.formatted_value(record)
|
|
174
|
-
classes =
|
|
176
|
+
classes = if field.value_for(record).nil?
|
|
177
|
+
"text-slate-400 dark:text-slate-500"
|
|
178
|
+
else
|
|
179
|
+
"text-slate-700 dark:text-slate-200"
|
|
180
|
+
end
|
|
175
181
|
|
|
176
182
|
tag.span(truncate(value, length: 80), class: classes)
|
|
177
183
|
end
|
|
@@ -189,7 +195,7 @@ module JSAdmin
|
|
|
189
195
|
end
|
|
190
196
|
|
|
191
197
|
def js_admin_sidebar_resource_label(resource)
|
|
192
|
-
resource.
|
|
198
|
+
resource.singular_label
|
|
193
199
|
end
|
|
194
200
|
|
|
195
201
|
def js_admin_sidebar_namespace_label(namespace)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="mb-8">
|
|
2
|
-
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"
|
|
3
|
-
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%=
|
|
2
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= t("js_admin.models.heading") %></h1>
|
|
3
|
+
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%= t("js_admin.models.available", count: @resources.size, formatted_count: number_with_delimiter(@resources.size)) %></p>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
6
|
<div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<div class="mb-8">
|
|
2
2
|
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
3
|
-
<%= link_to "
|
|
3
|
+
<%= link_to t("js_admin.navigation.home"), root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
4
4
|
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
5
|
<%= link_to @resource.display_name(@record), resource_path(@resource.model_id, @record), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
6
|
-
|
|
6
|
+
<%= t("js_admin.resources.actions.edit") %>
|
|
7
7
|
</div>
|
|
8
|
-
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"
|
|
8
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= t("js_admin.resources.headings.edit", record: @resource.display_name(@record)) %></h1>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<%= render "form", record: @record, resource: @resource, url: resource_path(@resource.model_id, @record) %>
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
</div>
|
|
32
32
|
|
|
33
33
|
<div class="flex items-center gap-2">
|
|
34
|
-
|
|
35
|
-
<%=
|
|
34
|
+
<% submit_label = record.persisted? ? t("js_admin.resources.actions.update", resource: resource.singular_label) : t("js_admin.resources.actions.create", resource: resource.singular_label) %>
|
|
35
|
+
<%= form.submit submit_label, class: "inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-400" %>
|
|
36
|
+
<%= link_to t("js_admin.resources.actions.cancel"), record.persisted? ? resource_path(resource.model_id, record) : resources_path(resource.model_id), class: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" %>
|
|
36
37
|
</div>
|
|
37
38
|
<% end %>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<div class="mb-8 flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
|
|
2
2
|
<div>
|
|
3
|
-
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400"><%= link_to "
|
|
3
|
+
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400"><%= link_to t("js_admin.navigation.home"), root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> / <%= @resource.label %></div>
|
|
4
4
|
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= @resource.label %></h1>
|
|
5
|
-
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%= number_with_delimiter(@total_count)
|
|
5
|
+
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%= t("js_admin.resources.records", count: @total_count, formatted_count: number_with_delimiter(@total_count)) %></p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
|
-
<%= link_to "
|
|
8
|
+
<%= link_to t("js_admin.resources.actions.new", resource: @resource.singular_label), new_resource_path(@resource.model_id), class: "inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-400" %>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<div class="overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<% @resource.list_fields.each do |field| %>
|
|
16
16
|
<th scope="col" class="whitespace-nowrap px-5 py-4 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"><%= field.human_name %></th>
|
|
17
17
|
<% end %>
|
|
18
|
-
<th scope="col" class="w-44 px-5 py-4 text-right text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
|
|
18
|
+
<th scope="col" class="w-44 px-5 py-4 text-right text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"><%= t("js_admin.resources.table.actions") %></th>
|
|
19
19
|
</tr>
|
|
20
20
|
</thead>
|
|
21
21
|
<tbody class="divide-y divide-slate-200 bg-white dark:divide-slate-800 dark:bg-slate-900">
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
<% end %>
|
|
27
27
|
<td class="whitespace-nowrap px-5 py-4 text-right text-sm">
|
|
28
28
|
<div class="flex items-center justify-end gap-3">
|
|
29
|
-
<%= link_to "
|
|
30
|
-
<%= link_to "
|
|
31
|
-
<%= button_to "
|
|
29
|
+
<%= link_to t("js_admin.resources.actions.view"), resource_path(@resource.model_id, record), class: "font-medium text-sky-700 hover:text-sky-900 dark:text-sky-300 dark:hover:text-sky-200" %>
|
|
30
|
+
<%= link_to t("js_admin.resources.actions.edit"), edit_resource_path(@resource.model_id, record), class: "font-medium text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-slate-100" %>
|
|
31
|
+
<%= button_to t("js_admin.resources.actions.delete"), resource_path(@resource.model_id, record), method: :delete, form: { class: "inline", data: { turbo_confirm: t("js_admin.resources.delete_confirmation") } }, class: "font-medium text-rose-700 hover:text-rose-900 dark:text-rose-300 dark:hover:text-rose-200" %>
|
|
32
32
|
</div>
|
|
33
33
|
</td>
|
|
34
34
|
</tr>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
<% if @records.empty? %>
|
|
38
38
|
<tr>
|
|
39
|
-
<td colspan="<%= @resource.list_fields.size + 1 %>" class="px-5 py-12 text-center text-sm text-slate-500 dark:text-slate-400"
|
|
39
|
+
<td colspan="<%= @resource.list_fields.size + 1 %>" class="px-5 py-12 text-center text-sm text-slate-500 dark:text-slate-400"><%= t("js_admin.resources.table.no_records") %></td>
|
|
40
40
|
</tr>
|
|
41
41
|
<% end %>
|
|
42
42
|
</tbody>
|
|
@@ -44,31 +44,31 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<div class="mt-6 flex justify-end">
|
|
47
|
-
<nav class="flex flex-wrap items-center justify-end gap-2" aria-label="
|
|
47
|
+
<nav class="flex flex-wrap items-center justify-end gap-2" aria-label="<%= t("js_admin.resources.pagination.label") %>">
|
|
48
48
|
<% if @page > 1 %>
|
|
49
|
-
<%= link_to "
|
|
49
|
+
<%= link_to t("js_admin.resources.pagination.first"), resources_path(@resource.model_id, page: 1), class: js_admin_pagination_link_classes %>
|
|
50
50
|
<% else %>
|
|
51
|
-
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"
|
|
51
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"><%= t("js_admin.resources.pagination.first") %></span>
|
|
52
52
|
<% end %>
|
|
53
53
|
|
|
54
54
|
<% if @resource.previous_page?(@page) %>
|
|
55
|
-
<%= link_to "
|
|
55
|
+
<%= link_to t("js_admin.resources.pagination.previous"), resources_path(@resource.model_id, page: @page - 1), class: js_admin_pagination_link_classes %>
|
|
56
56
|
<% else %>
|
|
57
|
-
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"
|
|
57
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"><%= t("js_admin.resources.pagination.previous") %></span>
|
|
58
58
|
<% end %>
|
|
59
59
|
|
|
60
|
-
<span class="<%= js_admin_pagination_status_classes %>" aria-current="page" aria-label="
|
|
60
|
+
<span class="<%= js_admin_pagination_status_classes %>" aria-current="page" aria-label="<%= t("js_admin.resources.pagination.status", page: @page, total_pages: @total_pages) %>"><%= @page %> / <%= @total_pages %></span>
|
|
61
61
|
|
|
62
62
|
<% if @resource.next_page?(@page) %>
|
|
63
|
-
<%= link_to "
|
|
63
|
+
<%= link_to t("js_admin.resources.pagination.next"), resources_path(@resource.model_id, page: @page + 1), class: js_admin_pagination_link_classes %>
|
|
64
64
|
<% else %>
|
|
65
|
-
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"
|
|
65
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"><%= t("js_admin.resources.pagination.next") %></span>
|
|
66
66
|
<% end %>
|
|
67
67
|
|
|
68
68
|
<% if @page < @total_pages %>
|
|
69
|
-
<%= link_to "
|
|
69
|
+
<%= link_to t("js_admin.resources.pagination.last"), resources_path(@resource.model_id, page: @total_pages), class: js_admin_pagination_link_classes %>
|
|
70
70
|
<% else %>
|
|
71
|
-
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"
|
|
71
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true"><%= t("js_admin.resources.pagination.last") %></span>
|
|
72
72
|
<% end %>
|
|
73
73
|
</nav>
|
|
74
74
|
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<div class="mb-8">
|
|
2
2
|
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
3
|
-
<%= link_to "
|
|
3
|
+
<%= link_to t("js_admin.navigation.home"), root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
4
4
|
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
|
-
|
|
5
|
+
<%= t("js_admin.resources.actions.new", resource: @resource.singular_label) %>
|
|
6
6
|
</div>
|
|
7
|
-
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"
|
|
7
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= t("js_admin.resources.headings.new", resource: @resource.singular_label) %></h1>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<%= render "form", record: @record, resource: @resource, url: resources_path(@resource.model_id) %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="mb-8 flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
|
|
2
2
|
<div>
|
|
3
3
|
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
4
|
-
<%= link_to "
|
|
4
|
+
<%= link_to t("js_admin.navigation.home"), root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
5
|
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
6
6
|
<%= @resource.display_name(@record) %>
|
|
7
7
|
</div>
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<div class="flex gap-2">
|
|
12
|
-
<%= link_to "
|
|
13
|
-
<%= button_to "
|
|
12
|
+
<%= link_to t("js_admin.resources.actions.edit"), edit_resource_path(@resource.model_id, @record), class: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" %>
|
|
13
|
+
<%= button_to t("js_admin.resources.actions.delete"), resource_path(@resource.model_id, @record), method: :delete, form: { data: { turbo_confirm: t("js_admin.resources.delete_confirmation") } }, class: "inline-flex items-center justify-center rounded-md bg-rose-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-rose-700 dark:bg-rose-500 dark:hover:bg-rose-400" %>
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
</nav>
|
|
26
26
|
|
|
27
27
|
<div class="shrink-0 border-t border-slate-200 px-5 py-4 dark:border-slate-800 sm:px-8 lg:px-5">
|
|
28
|
-
<button type="button" data-js-admin-theme-target="toggle" data-action="js-admin-theme#toggle" class="inline-flex h-9 w-full items-center justify-between rounded-md border border-slate-300 bg-white px-3 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" aria-label="
|
|
28
|
+
<button type="button" data-js-admin-theme-target="toggle" data-action="js-admin-theme#toggle" class="inline-flex h-9 w-full items-center justify-between rounded-md border border-slate-300 bg-white px-3 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" aria-label="<%= t("js_admin.theme.toggle_aria_label") %>" aria-pressed="false">
|
|
29
29
|
<span class="flex items-center gap-2">
|
|
30
30
|
<span class="h-2.5 w-2.5 rounded-full bg-slate-500 dark:bg-amber-300"></span>
|
|
31
|
-
<span
|
|
31
|
+
<span><%= t("js_admin.theme.label") %></span>
|
|
32
32
|
</span>
|
|
33
|
-
<span class="text-xs text-slate-400 dark:text-slate-500"
|
|
33
|
+
<span class="text-xs text-slate-400 dark:text-slate-500"><%= t("js_admin.theme.toggle") %></span>
|
|
34
34
|
</button>
|
|
35
35
|
</div>
|
|
36
36
|
</aside>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
en:
|
|
2
|
+
js_admin:
|
|
3
|
+
configuration:
|
|
4
|
+
errors:
|
|
5
|
+
incomplete_basic_auth: "Basic Auth username and password must both be configured."
|
|
6
|
+
multiple_authentication_strategies: "Basic Auth and authenticate_with cannot be configured at the same time."
|
|
7
|
+
authorization_without_authentication: "authorize_with requires authenticate_with."
|
|
8
|
+
generator:
|
|
9
|
+
options:
|
|
10
|
+
path: "Mount path for JSAdmin"
|
|
11
|
+
warnings:
|
|
12
|
+
importmap_missing: "config/importmap.rb was not found. Run bin/rails importmap:install, then rerun js_admin:install."
|
|
13
|
+
titles:
|
|
14
|
+
models: "JSAdmin Models"
|
|
15
|
+
new: "New %{resource}"
|
|
16
|
+
edit: "Edit %{record}"
|
|
17
|
+
navigation:
|
|
18
|
+
home: "Home"
|
|
19
|
+
models:
|
|
20
|
+
heading: "Models"
|
|
21
|
+
available:
|
|
22
|
+
one: "%{formatted_count} model available"
|
|
23
|
+
other: "%{formatted_count} models available"
|
|
24
|
+
resources:
|
|
25
|
+
notices:
|
|
26
|
+
created: "%{resource} was created."
|
|
27
|
+
updated: "%{resource} was updated."
|
|
28
|
+
deleted: "%{resource} was deleted."
|
|
29
|
+
records:
|
|
30
|
+
one: "%{formatted_count} record"
|
|
31
|
+
other: "%{formatted_count} records"
|
|
32
|
+
headings:
|
|
33
|
+
new: "New %{resource}"
|
|
34
|
+
edit: "Edit %{record}"
|
|
35
|
+
actions:
|
|
36
|
+
create: "Create %{resource}"
|
|
37
|
+
update: "Update %{resource}"
|
|
38
|
+
new: "New %{resource}"
|
|
39
|
+
view: "View"
|
|
40
|
+
edit: "Edit"
|
|
41
|
+
delete: "Delete"
|
|
42
|
+
cancel: "Cancel"
|
|
43
|
+
table:
|
|
44
|
+
actions: "Actions"
|
|
45
|
+
no_records: "No records"
|
|
46
|
+
delete_confirmation: "Delete this record?"
|
|
47
|
+
pagination:
|
|
48
|
+
label: "Pagination"
|
|
49
|
+
first: "First"
|
|
50
|
+
previous: "Previous"
|
|
51
|
+
next: "Next"
|
|
52
|
+
last: "Last"
|
|
53
|
+
status: "Page %{page} of %{total_pages}"
|
|
54
|
+
theme:
|
|
55
|
+
label: "Theme"
|
|
56
|
+
toggle: "Toggle"
|
|
57
|
+
toggle_aria_label: "Toggle dark mode"
|
|
58
|
+
values:
|
|
59
|
+
null: "NULL"
|
|
60
|
+
"true": "true"
|
|
61
|
+
"false": "false"
|
|
62
|
+
resource:
|
|
63
|
+
fallback_display_name: "%{resource} #%{id}"
|
|
64
|
+
errors:
|
|
65
|
+
unknown_resource: "Unknown JSAdmin resource: %{model_id}"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
ko:
|
|
2
|
+
js_admin:
|
|
3
|
+
configuration:
|
|
4
|
+
errors:
|
|
5
|
+
incomplete_basic_auth: "Basic Auth username과 password를 모두 설정해야 합니다."
|
|
6
|
+
multiple_authentication_strategies: "Basic Auth와 authenticate_with는 동시에 설정할 수 없습니다."
|
|
7
|
+
authorization_without_authentication: "authorize_with를 사용하려면 authenticate_with가 필요합니다."
|
|
8
|
+
generator:
|
|
9
|
+
options:
|
|
10
|
+
path: "JSAdmin mount 경로"
|
|
11
|
+
warnings:
|
|
12
|
+
importmap_missing: "config/importmap.rb를 찾을 수 없습니다. bin/rails importmap:install 실행 후 js_admin:install을 다시 실행하세요."
|
|
13
|
+
titles:
|
|
14
|
+
models: "JSAdmin 모델"
|
|
15
|
+
new: "새 %{resource}"
|
|
16
|
+
edit: "%{record} 편집"
|
|
17
|
+
navigation:
|
|
18
|
+
home: "홈"
|
|
19
|
+
models:
|
|
20
|
+
heading: "모델"
|
|
21
|
+
available:
|
|
22
|
+
one: "사용 가능한 모델 %{formatted_count}개"
|
|
23
|
+
other: "사용 가능한 모델 %{formatted_count}개"
|
|
24
|
+
resources:
|
|
25
|
+
notices:
|
|
26
|
+
created: "%{resource} 레코드를 생성했습니다."
|
|
27
|
+
updated: "%{resource} 레코드를 수정했습니다."
|
|
28
|
+
deleted: "%{resource} 레코드를 삭제했습니다."
|
|
29
|
+
records:
|
|
30
|
+
one: "레코드 %{formatted_count}개"
|
|
31
|
+
other: "레코드 %{formatted_count}개"
|
|
32
|
+
headings:
|
|
33
|
+
new: "새 %{resource}"
|
|
34
|
+
edit: "%{record} 편집"
|
|
35
|
+
actions:
|
|
36
|
+
create: "%{resource} 생성"
|
|
37
|
+
update: "%{resource} 저장"
|
|
38
|
+
new: "새 %{resource}"
|
|
39
|
+
view: "보기"
|
|
40
|
+
edit: "편집"
|
|
41
|
+
delete: "삭제"
|
|
42
|
+
cancel: "취소"
|
|
43
|
+
table:
|
|
44
|
+
actions: "작업"
|
|
45
|
+
no_records: "레코드가 없습니다"
|
|
46
|
+
delete_confirmation: "이 레코드를 삭제하시겠습니까?"
|
|
47
|
+
pagination:
|
|
48
|
+
label: "페이지 탐색"
|
|
49
|
+
first: "처음"
|
|
50
|
+
previous: "이전"
|
|
51
|
+
next: "다음"
|
|
52
|
+
last: "마지막"
|
|
53
|
+
status: "전체 %{total_pages}페이지 중 %{page}페이지"
|
|
54
|
+
theme:
|
|
55
|
+
label: "테마"
|
|
56
|
+
toggle: "전환"
|
|
57
|
+
toggle_aria_label: "다크 모드 전환"
|
|
58
|
+
values:
|
|
59
|
+
null: "NULL"
|
|
60
|
+
"true": "참"
|
|
61
|
+
"false": "거짓"
|
|
62
|
+
resource:
|
|
63
|
+
fallback_display_name: "%{resource} #%{id}"
|
|
64
|
+
errors:
|
|
65
|
+
unknown_resource: "알 수 없는 JSAdmin 리소스입니다: %{model_id}"
|
|
@@ -16,7 +16,7 @@ module JSAdmin
|
|
|
16
16
|
class_option :path,
|
|
17
17
|
type: :string,
|
|
18
18
|
default: "/admin",
|
|
19
|
-
desc: "
|
|
19
|
+
desc: I18n.t("js_admin.generator.options.path")
|
|
20
20
|
|
|
21
21
|
def mount_engine
|
|
22
22
|
route %(mount JSAdmin::Engine => "#{options[:path]}")
|
|
@@ -36,10 +36,17 @@ module JSAdmin
|
|
|
36
36
|
# Methods tried in order when displaying records in labels and associations.
|
|
37
37
|
# config.display_name_methods = %i[name title email username id]
|
|
38
38
|
|
|
39
|
+
# Render a column as a select. Callables are evaluated on every form render.
|
|
40
|
+
# config.select_options "Post", category: -> { Category.pluck(:name) }
|
|
41
|
+
|
|
39
42
|
# Basic Auth is disabled unless both username and password are present.
|
|
40
43
|
# config.basic_auth_username = ENV["JS_ADMIN_BASIC_AUTH_USERNAME"]
|
|
41
44
|
# config.basic_auth_password = ENV["JS_ADMIN_BASIC_AUTH_PASSWORD"]
|
|
42
45
|
# config.basic_auth_realm = "JSAdmin"
|
|
46
|
+
|
|
47
|
+
# Or delegate authentication and authorization to the host application.
|
|
48
|
+
# config.authenticate_with { authenticate_user! }
|
|
49
|
+
# config.authorize_with { current_user&.admin? }
|
|
43
50
|
end
|
|
44
51
|
RUBY
|
|
45
52
|
end
|
|
@@ -48,7 +55,7 @@ module JSAdmin
|
|
|
48
55
|
importmap_path = application_path("config/importmap.rb")
|
|
49
56
|
|
|
50
57
|
unless File.exist?(importmap_path)
|
|
51
|
-
say_status :warning, "
|
|
58
|
+
say_status :warning, I18n.t("js_admin.generator.warnings.importmap_missing"), :yellow
|
|
52
59
|
return
|
|
53
60
|
end
|
|
54
61
|
|
|
@@ -5,19 +5,25 @@ module JSAdmin
|
|
|
5
5
|
:records_per_page,
|
|
6
6
|
:association_limit,
|
|
7
7
|
:display_name_methods,
|
|
8
|
+
:field_select_options,
|
|
8
9
|
:basic_auth_username,
|
|
9
10
|
:basic_auth_password,
|
|
10
11
|
:basic_auth_realm
|
|
11
12
|
|
|
13
|
+
attr_reader :authentication_callback, :authorization_callback
|
|
14
|
+
|
|
12
15
|
def initialize
|
|
13
16
|
@included_models = []
|
|
14
17
|
@excluded_models = []
|
|
15
18
|
@records_per_page = 10
|
|
16
19
|
@association_limit = 100
|
|
17
20
|
@display_name_methods = %i[name title email username id]
|
|
21
|
+
@field_select_options = {}
|
|
18
22
|
@basic_auth_username = nil
|
|
19
23
|
@basic_auth_password = nil
|
|
20
24
|
@basic_auth_realm = "JSAdmin"
|
|
25
|
+
@authentication_callback = nil
|
|
26
|
+
@authorization_callback = nil
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def include_models(*models)
|
|
@@ -28,12 +34,72 @@ module JSAdmin
|
|
|
28
34
|
excluded_models.concat(normalize_model_names(models))
|
|
29
35
|
end
|
|
30
36
|
|
|
37
|
+
def select_options(model, **options_by_field)
|
|
38
|
+
model_options = field_select_options[normalize_model_names([ model ]).first] ||= {}
|
|
39
|
+
model_options.merge!(options_by_field.transform_keys(&:to_s))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def select_options_for(model, field_name)
|
|
43
|
+
field_select_options.dig(normalize_model_names([ model ]).first, field_name.to_s)
|
|
44
|
+
end
|
|
45
|
+
|
|
31
46
|
def basic_auth_enabled?
|
|
32
|
-
|
|
47
|
+
basic_auth_username_configured? && basic_auth_password_configured?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def authenticate_with(&callback)
|
|
51
|
+
@authentication_callback = callback
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def authorize_with(&callback)
|
|
55
|
+
@authorization_callback = callback
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def authentication_strategy
|
|
59
|
+
validate!
|
|
60
|
+
|
|
61
|
+
return :callback if authentication_callback
|
|
62
|
+
return :basic_auth if basic_auth_enabled?
|
|
63
|
+
|
|
64
|
+
:none
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate!
|
|
68
|
+
validate_basic_auth_credentials!
|
|
69
|
+
validate_authentication_strategy!
|
|
70
|
+
validate_authorization_callback!
|
|
71
|
+
|
|
72
|
+
self
|
|
33
73
|
end
|
|
34
74
|
|
|
35
75
|
private
|
|
36
76
|
|
|
77
|
+
def validate_basic_auth_credentials!
|
|
78
|
+
return if basic_auth_username_configured? == basic_auth_password_configured?
|
|
79
|
+
|
|
80
|
+
raise ConfigurationError, I18n.t("js_admin.configuration.errors.incomplete_basic_auth")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_authentication_strategy!
|
|
84
|
+
return unless basic_auth_enabled? && authentication_callback
|
|
85
|
+
|
|
86
|
+
raise ConfigurationError, I18n.t("js_admin.configuration.errors.multiple_authentication_strategies")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_authorization_callback!
|
|
90
|
+
return unless authorization_callback && !authentication_callback
|
|
91
|
+
|
|
92
|
+
raise ConfigurationError, I18n.t("js_admin.configuration.errors.authorization_without_authentication")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def basic_auth_username_configured?
|
|
96
|
+
basic_auth_username.to_s != ""
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def basic_auth_password_configured?
|
|
100
|
+
basic_auth_password.to_s != ""
|
|
101
|
+
end
|
|
102
|
+
|
|
37
103
|
def normalize_model_names(models)
|
|
38
104
|
models.flatten.map { |model| model.is_a?(Class) ? model.name : model.to_s }
|
|
39
105
|
end
|
data/lib/js_admin/field.rb
CHANGED
|
@@ -36,6 +36,7 @@ module JSAdmin
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def input_kind
|
|
39
|
+
return :configured_select if configured_select?
|
|
39
40
|
return :association_select if belongs_to_association
|
|
40
41
|
return :enum_select if enum?
|
|
41
42
|
|
|
@@ -65,6 +66,15 @@ module JSAdmin
|
|
|
65
66
|
resource.model_class.defined_enums.fetch(name, {}).keys
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
def configured_select?
|
|
70
|
+
!configured_select_source.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def configured_select_values
|
|
74
|
+
source = configured_select_source
|
|
75
|
+
Array(source.respond_to?(:call) ? source.call : source)
|
|
76
|
+
end
|
|
77
|
+
|
|
68
78
|
def belongs_to_association
|
|
69
79
|
@belongs_to_association ||= resource.model_class
|
|
70
80
|
.reflect_on_all_associations(:belongs_to)
|
|
@@ -98,11 +108,11 @@ module JSAdmin
|
|
|
98
108
|
|
|
99
109
|
case value
|
|
100
110
|
when nil
|
|
101
|
-
"
|
|
111
|
+
I18n.t("js_admin.values.null")
|
|
102
112
|
when true
|
|
103
|
-
"true"
|
|
113
|
+
I18n.t("js_admin.values.true")
|
|
104
114
|
when false
|
|
105
|
-
"false"
|
|
115
|
+
I18n.t("js_admin.values.false")
|
|
106
116
|
else
|
|
107
117
|
value.to_s
|
|
108
118
|
end
|
|
@@ -110,6 +120,10 @@ module JSAdmin
|
|
|
110
120
|
|
|
111
121
|
private
|
|
112
122
|
|
|
123
|
+
def configured_select_source
|
|
124
|
+
JSAdmin.configuration.select_options_for(resource.model_class, name)
|
|
125
|
+
end
|
|
126
|
+
|
|
113
127
|
def inheritance_column?
|
|
114
128
|
name == resource.model_class.inheritance_column
|
|
115
129
|
end
|
data/lib/js_admin/registry.rb
CHANGED
|
@@ -7,7 +7,10 @@ module JSAdmin
|
|
|
7
7
|
|
|
8
8
|
def resource_for!(model_id)
|
|
9
9
|
resources.find { |resource| resource.model_id == model_id } ||
|
|
10
|
-
raise(
|
|
10
|
+
raise(
|
|
11
|
+
ActiveRecord::RecordNotFound,
|
|
12
|
+
I18n.t("js_admin.errors.unknown_resource", model_id: model_id)
|
|
13
|
+
)
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
def reset!
|
data/lib/js_admin/resource.rb
CHANGED
|
@@ -69,7 +69,11 @@ module JSAdmin
|
|
|
69
69
|
|
|
70
70
|
return record.public_send(display_method).to_s if display_method
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
I18n.t(
|
|
73
|
+
"js_admin.resource.fallback_display_name",
|
|
74
|
+
resource: singular_label,
|
|
75
|
+
id: record.id
|
|
76
|
+
)
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
def next_page?(page)
|
data/lib/js_admin/version.rb
CHANGED
data/lib/js_admin.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "stimulus-rails"
|
|
|
5
5
|
require "tailwindcss-rails"
|
|
6
6
|
require "turbo-rails"
|
|
7
7
|
|
|
8
|
+
require "js_admin/configuration_error"
|
|
8
9
|
require "js_admin/configuration"
|
|
9
10
|
require "js_admin/field"
|
|
10
11
|
require "js_admin/registry"
|
|
@@ -19,6 +20,7 @@ module JSAdmin
|
|
|
19
20
|
|
|
20
21
|
def configure
|
|
21
22
|
yield configuration
|
|
23
|
+
configuration.validate!
|
|
22
24
|
Registry.reset!
|
|
23
25
|
end
|
|
24
26
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: js_admin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- JoungSik
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -150,10 +150,13 @@ files:
|
|
|
150
150
|
- app/views/layouts/js_admin/_flash_messages.html.erb
|
|
151
151
|
- app/views/layouts/js_admin/_sidebar.html.erb
|
|
152
152
|
- app/views/layouts/js_admin/application.html.erb
|
|
153
|
+
- config/locales/js_admin.en.yml
|
|
154
|
+
- config/locales/js_admin.ko.yml
|
|
153
155
|
- config/routes.rb
|
|
154
156
|
- lib/generators/js_admin/install/install_generator.rb
|
|
155
157
|
- lib/js_admin.rb
|
|
156
158
|
- lib/js_admin/configuration.rb
|
|
159
|
+
- lib/js_admin/configuration_error.rb
|
|
157
160
|
- lib/js_admin/engine.rb
|
|
158
161
|
- lib/js_admin/field.rb
|
|
159
162
|
- lib/js_admin/registry.rb
|