fino-rails 1.0.4 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7a56251fd94a51a2bd2725b8e6f2ab6258f719226ca136e7d7411254319d57a
4
- data.tar.gz: 33cfed847bdd658ebf88c3743c1193fad57a6981a9c815e177d875474b0c93e9
3
+ metadata.gz: 82df870b89c465c8145e36f27773ef32f7d402c5f3193137c9ec5cd889bdb4cf
4
+ data.tar.gz: 8009126552b6e9dcaf88a02272e6864be27f7c68cf8ecca4247be29b3376146d
5
5
  SHA512:
6
- metadata.gz: 25450bbbb1a24050116f6ec49235d345ef78130328baf52d2d335e4205bf6b6f5d4d2362898a8210de4523dd1aca1075c7acf3e16e5ee2020a3c3aef19b6e517
7
- data.tar.gz: 29feda12bb2bdaa16357952868986a77adebf88767e62751e2e750fbe315c7f403e334c7727d37656116561f1f786eb0d446edea48767ee223c5b3a279d9f824
6
+ metadata.gz: a37eff3a46c93b669bd10bc8df8c238b7019d4a19566b72a5df69bec2ed6eb75de43b4aa3cb2590dec090da5333efeef347eec89ceedf214b9eebbbed47cabf3
7
+ data.tar.gz: 85de226051a1472e5d61ac18b398f781653d283457e449913eb7f744328aaba9dfb1da620aed4f636d032fb28108718b7d3bfc3700a263a04d7e2684a45c79a2
data/README.md CHANGED
@@ -56,7 +56,7 @@ Fino.value(:temperature, at: :openai) #=> 0.7
56
56
 
57
57
  Fino.values(:model, :temperature, at: :openai) #=> ["gpt-4", 0.7]
58
58
 
59
- Fino.set("gpt-5", :model, at: :openai)
59
+ Fino.set(model: "gpt-5", at: :openai)
60
60
  Fino.value(:model, at: :openai) #=> "gpt-5"
61
61
  ```
62
62
 
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -1,4 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Fino::Rails::ApplicationController < ActionController::Base
4
+ GENERAL_SECTION = Fino::Definition::Section.new(name: nil, label: "General")
5
+
6
+ helper Fino::Rails::ApplicationHelper
7
+ helper Fino::Rails::SettingsHelper
8
+
9
+ def sections
10
+ @sections ||= [
11
+ GENERAL_SECTION,
12
+ *Fino.registry.section_definitions
13
+ ]
14
+ end
15
+
16
+ def current_section
17
+ return @current_section if defined?(@current_section)
18
+
19
+ section_name = params[:section]&.to_sym
20
+
21
+ @current_section =
22
+ case section_name
23
+ when :general
24
+ GENERAL_SECTION
25
+ else
26
+ Fino.registry.section_definitions.find { |s| s.name == params[:section]&.to_sym }
27
+ end
28
+ end
29
+
30
+ helper_method :sections, :current_section
4
31
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fino::Rails::DashboardController < Fino::Rails::ApplicationController
4
+ def index
5
+ @settings = Fino.settings
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fino::Rails::SectionsController < Fino::Rails::ApplicationController
4
+ def show
5
+ @settings = Fino.settings(at: current_section.name)
6
+ end
7
+ end
@@ -2,19 +2,15 @@
2
2
 
3
3
  class Fino::Rails::SettingsController < Fino::Rails::ApplicationController
4
4
  def index
5
- @settings = Fino.library.all
5
+ @settings = Fino.settings
6
6
  end
7
7
 
8
8
  def edit
9
- setting_name, at = parse_setting_path(params[:key])
10
-
11
- @setting = Fino.setting(setting_name, at: at)
9
+ @setting = Fino.setting(setting_name, at: section_name)
12
10
  end
13
11
 
14
12
  def update
15
- setting_name, at = parse_setting_path(params[:key])
16
-
17
- Fino.set(params[:value], setting_name, at: at)
13
+ Fino.set(setting_name => params[:value], at: section_name)
18
14
 
19
15
  redirect_to root_path, notice: "Setting updated successfully"
20
16
  rescue Fino::Registry::UnknownSetting
@@ -23,7 +19,14 @@ class Fino::Rails::SettingsController < Fino::Rails::ApplicationController
23
19
 
24
20
  private
25
21
 
26
- def parse_setting_path(key)
27
- key.split("/").map(&:to_sym).reverse
22
+ def setting_name
23
+ params[:setting]
24
+ end
25
+
26
+ def section_name
27
+ case params[:section]
28
+ when "general" then nil
29
+ else params[:section]
30
+ end
28
31
  end
29
32
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fino::Rails::ApplicationHelper
4
+ def fino_asset_path(file, version: true)
5
+ path = "#{Rails.application.config.relative_url_root}/fino-assets/#{file}".gsub("//", "/")
6
+ version ? "#{path}?v=#{Fino::VERSION}" : path
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fino::Rails::SettingsHelper
4
+ SETTING_TYPE_TO_COLOR_MAPPING = {
5
+ string: "pink",
6
+ boolean: "blue",
7
+ integer: "yellow",
8
+ float: "purple"
9
+ }
10
+
11
+ def setting_type_label(setting)
12
+ color = SETTING_TYPE_TO_COLOR_MAPPING.fetch(setting.type, "gray")
13
+
14
+ tag.div class: "flex-none rounded-full bg-#{color}-50 px-2 py-1 text-xs font-medium text-#{color}-700 inset-ring inset-ring-#{color}-700/10" do
15
+ setting.type.to_s.titleize
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ <div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-6 border-b border-gray-200 bg-white px-4 sm:px-6 lg:px-8">
2
+ <button type="button" command="show-modal" commandfor="sidebar" class="-m-2.5 p-2.5 text-gray-900 xl:hidden">
3
+ <span class="sr-only">Open sidebar</span>
4
+ <svg viewBox="0 0 20 20" fill="currentColor" data-slot="icon" aria-hidden="true" class="size-5">
5
+ <path d="M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" fill-rule="evenodd" />
6
+ </svg>
7
+ </button>
8
+
9
+ <div class="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
10
+ <form action="#" method="GET" class="grid flex-1 grid-cols-1">
11
+ <input name="search" placeholder="Search" aria-label="Search" class="col-start-1 row-start-1 block size-full bg-transparent pl-8 text-base text-gray-900 outline-hidden placeholder:text-gray-400 sm:text-sm/6" />
12
+ <svg viewBox="0 0 20 20" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 size-5 self-center text-gray-400">
13
+ <path d="M9 3.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11ZM2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-3.328A7 7 0 0 1 2 9Z" clip-rule="evenodd" fill-rule="evenodd" />
14
+ </svg>
15
+ </form>
16
+ </div>
17
+ </div>
@@ -0,0 +1,33 @@
1
+ <div class="hidden xl:fixed xl:inset-y-0 xl:z-50 xl:flex xl:w-72 xl:flex-col">
2
+ <div class="flex grow flex-col gap-y-5 overflow-y-auto bg-gray-50 px-4 ring-1 ring-gray-200">
3
+ <div class="flex h-16 shrink-0 items-center">
4
+ <%= image_tag "/fino-assets/logo.svg", class: "h-8 w-auto", alt: "Fino" %>
5
+ </div>
6
+
7
+ <nav class="flex flex-1 flex-col gap-y-7">
8
+ <div>
9
+ <%= link_to root_path, class: "flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold #{current_page?(:root) ? 'text-indigo-600' : 'text-gray-700'} hover:bg-gray-100 hover:text-indigo-600" do %>
10
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" data-slot="icon" aria-hidden="true" class="size-6 shrink-0 text-indigo-600">
11
+ <path d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" stroke-linecap="round" stroke-linejoin="round" />
12
+ </svg>
13
+ Dashboard
14
+ <% end %>
15
+ </div>
16
+
17
+ <div>
18
+ <div class="text-xs/6 font-semibold text-gray-400">Sections</div>
19
+ <ul role="list" class="space-y-1">
20
+ <% sections.each do |section_definition| %>
21
+ <li>
22
+ <%= link_to settings_section_path(section: section_definition.name || :general), class: "group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold #{current_section && (current_section.name == section_definition.name) ? 'text-indigo-600' : 'text-gray-700'} hover:bg-gray-100 hover:text-indigo-600" do %>
23
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-white text-[0.625rem] font-medium text-gray-400 group-hover:border-indigo-600 group-hover:text-indigo-600"><%= section_definition.label.first %></span>
24
+ <span class="break-words"><%= section_definition.label %></span>
25
+ <span aria-hidden="true" class="ml-auto w-9 min-w-max rounded-full bg-white px-2.5 py-0.5 text-center text-xs/5 font-medium whitespace-nowrap text-gray-600 outline-1 -outline-offset-1 outline-gray-200"><%=Fino.settings(at: section_definition.name).count %></span>
26
+ <% end %>
27
+ </li>
28
+ <% end %>
29
+ </ul>
30
+ </div>
31
+ </nav>
32
+ </div>
33
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="m-3">
2
+ <ul role="list" class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
3
+ <% @settings.each do |setting| %>
4
+ <li class="col-span-1 divide-y divide-gray-200 rounded-lg bg-white border-1 border-gray-200">
5
+ <%= render "fino/rails/settings/setting", locals: { setting: setting } %>
6
+ </li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
@@ -0,0 +1,11 @@
1
+ <% content_for(:title, "#{current_section.label}") %>
2
+
3
+ <div class="m-3">
4
+ <ul role="list" class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
5
+ <% @settings.each do |setting| %>
6
+ <li class="col-span-1 divide-y divide-gray-200 rounded-lg bg-white border-1 border-gray-200">
7
+ <%= render "fino/rails/settings/setting", locals: { setting: setting } %>
8
+ </li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
@@ -0,0 +1,52 @@
1
+ <% setting = locals.fetch(:setting) %>
2
+
3
+ <%= link_to edit_setting_path(section: setting.section_name || :general, setting: setting.name) do %>
4
+ <div class="flex w-full h-full justify-between p-6 hover:bg-gray-50 transition-colors rounded-lg">
5
+ <div class="flex flex-col space-y-3 justify-between">
6
+ <div class="flex flex-col gap-1">
7
+ <div class="flex items-center gap-x-3">
8
+ <h2 class="min-w-0 text-sm/6 font-semibold text-gray-900">
9
+ <div class="flex gap-x-2">
10
+ <span class="truncate"><%= setting.definition.section_definition&.label || "General" %></span>
11
+ <span class="text-gray-500">/</span>
12
+ <span class="whitespace-nowrap"><%= setting.name %></span>
13
+ </div>
14
+ </h2>
15
+
16
+ <%= setting_type_label(setting) %>
17
+ </div>
18
+
19
+ <% if setting.description.present? %>
20
+ <p class="text-sm text-gray-500 whitespace-normal"><%= setting.description %></p>
21
+ <% end %>
22
+ </div>
23
+
24
+ <div class="">
25
+ <% case setting %>
26
+ <% when Fino::Settings::Boolean %>
27
+ <div class="flex items-center gap-x-2">
28
+ <div class="flex-none rounded-full p-1 <%= setting.value ? 'text-green-500 bg-green-100' : 'text-red-500 bg-red-100' %>">
29
+ <div class="size-2 rounded-full bg-current"></div>
30
+ </div>
31
+
32
+ <span class="text-lg font-medium text-gray-800"><%= setting.value ? 'Enabled' : 'Disabled' %></span>
33
+ </div>
34
+ <% when Fino::Settings::String %>
35
+ <div class="truncate text-xl font-semibold text-gray-800">
36
+ "<%= setting.value %>"
37
+ </div>
38
+ <% else %>
39
+ <div class="truncate text-xl font-semibold text-gray-800">
40
+ <%= setting.value %>
41
+ </div>
42
+ <% end %>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="flex items-center">
47
+ <svg viewBox="0 0 20 20" fill="currentColor" data-slot="icon" aria-hidden="true" class="size-5 flex-none text-gray-400">
48
+ <path d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
49
+ </svg>
50
+ </div>
51
+ </div>
52
+ <% end %>
@@ -1,179 +1,116 @@
1
- <% content_for(:title, "Edit #{@setting.name}") %>
2
- <% content_for(:head) do %>
3
- <style>
4
- .form-group {
5
- margin-bottom: 1.5rem;
6
- }
7
-
8
- .form-label {
9
- display: block;
10
- font-size: 0.875rem;
11
- font-weight: 500;
12
- color: #0f172a;
13
- margin-bottom: 0.5rem;
14
- }
15
-
16
- .form-input {
17
- display: block;
18
- width: 100%;
19
- padding: 0.5rem 0.75rem;
20
- font-size: 0.875rem;
21
- line-height: 1.5;
22
- color: #0f172a;
23
- background-color: #ffffff;
24
- border: 1px solid #e2e8f0;
25
- border-radius: 6px;
26
- transition: border-color 0.15s ease-in-out;
27
- }
28
-
29
- .form-input:focus {
30
- outline: none;
31
- border-color: #0f172a;
32
- box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.1);
33
- }
34
-
35
- .form-select {
36
- display: block;
37
- width: 100%;
38
- padding: 0.5rem 0.75rem;
39
- font-size: 0.875rem;
40
- line-height: 1.5;
41
- color: #0f172a;
42
- background-color: #ffffff;
43
- border: 1px solid #e2e8f0;
44
- border-radius: 6px;
45
- transition: border-color 0.15s ease-in-out;
46
- }
47
-
48
- .form-select:focus {
49
- outline: none;
50
- border-color: #0f172a;
51
- box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.1);
52
- }
53
-
54
- .form-checkbox {
55
- width: 1rem;
56
- height: 1rem;
57
- border: 1px solid #e2e8f0;
58
- border-radius: 4px;
59
- margin-right: 0.5rem;
60
- }
61
-
62
- .form-help {
63
- font-size: 0.75rem;
64
- color: #64748b;
65
- margin-top: 0.25rem;
66
- }
67
-
68
- .button-group {
69
- display: flex;
70
- gap: 0.75rem;
71
- margin-top: 2rem;
72
- }
73
-
74
- .breadcrumb {
75
- margin-bottom: 1.5rem;
76
- font-size: 0.875rem;
77
- color: #64748b;
78
- }
79
-
80
- .breadcrumb a {
81
- color: #0f172a;
82
- text-decoration: none;
83
- }
84
-
85
- .breadcrumb a:hover {
86
- text-decoration: underline;
87
- }
88
-
89
- .setting-info {
90
- background-color: #f8fafc;
91
- border: 1px solid #e2e8f0;
92
- border-radius: 6px;
93
- padding: 1rem;
94
- margin-bottom: 1.5rem;
95
- }
96
-
97
- .setting-info-title {
98
- font-weight: 600;
99
- margin-bottom: 0.5rem;
100
- color: #0f172a;
101
- }
102
-
103
- .setting-info-detail {
104
- font-size: 0.875rem;
105
- color: #64748b;
106
- margin-bottom: 0.25rem;
107
- }
108
- </style>
109
- <% end %>
110
-
111
- <div class="breadcrumb">
112
- <%= link_to "Settings", root_path %> / Edit <%= @setting.name %>
113
- </div>
114
-
115
- <div class="header">
116
- <h1>Edit Setting</h1>
117
- <p>Modify the configuration value for this setting</p>
118
- </div>
119
-
120
- <div class="card">
121
- <div class="card-header">
122
- <h2 class="card-title"><%= @setting.name %></h2>
123
- </div>
124
- <div class="card-content">
125
- <div class="setting-info">
126
- <div class="setting-info-title">Setting Information</div>
127
- <div class="setting-info-detail"><strong>Type:</strong> <%= @setting.class.name.demodulize %></div>
128
- <% if @setting.section_name %>
129
- <div class="setting-info-detail"><strong>Section:</strong> <%= @setting.section_name %></div>
130
- <% end %>
131
- <div class="setting-info-detail"><strong>Default:</strong> <code><%= @setting.definition.default.inspect %></code></div>
132
- <% if @setting.definition.options[:description].present? %>
133
- <div class="setting-info-detail"><strong>Description:</strong> <%= @setting.definition.options[:description] %></div>
134
- <% end %>
135
- </div>
136
-
137
- <%= form_with url: update_setting_path(@setting.key), method: :put, local: true do |f| %>
138
- <div class="form-group">
139
- <%= f.label :value, class: "form-label" do %>
140
- Current Value
141
- <% if @setting.definition.options[:description].present? %>
142
- <div class="form-help"><%= @setting.definition.options[:description] %></div>
143
- <% end %>
144
- <% end %>
145
-
146
- <% case @setting.class.name.demodulize.downcase %>
147
- <% when 'boolean' %>
148
- <%= f.check_box :value,
149
- { class: "form-checkbox", checked: @setting.value },
150
- '1',
151
- '0' %>
152
- <% when 'integer' %>
153
- <%= f.number_field :value,
154
- value: @setting.value,
155
- class: "form-input",
156
- step: 1 %>
157
- <% when 'float' %>
158
- <%= f.number_field :value,
159
- value: @setting.value,
160
- class: "form-input",
161
- step: 0.1 %>
162
- <% else %>
163
- <%= f.text_field :value,
164
- value: @setting.value,
165
- class: "form-input" %>
166
- <% end %>
1
+ <% content_for(:title, @setting.key) %>
2
+
3
+ <div class="m-3">
4
+ <div class="container mx-auto md:w-xl px-0 pt-10">
5
+ <div class="bg-white shadow-xs outline outline-gray-900/5 sm:rounded-xl md:col-span-2">
6
+ <%= form_with url: update_setting_path(section: @setting.section_name || :general, setting: @setting.name), method: :put, local: true do |f| %>
7
+ <div class="border-b border-gray-100 p-3 md:p-5 flex justify-between">
8
+ <div class="flex self-center">
9
+ <h3 class="text-base font-semibold text-gray-900"><%= link_to @setting.section_definition&.label || "General", settings_section_path(section: @setting.section_name || :general) %></h3>
10
+ <span class="mx-2 text-gray-400">/</span>
11
+ <h3 class="text-base font-semibold text-gray-900"><%= @setting.name %></h3>
12
+ </div>
13
+
14
+ <div class="flex gap-3 lg:mt-0 lg:ml-4">
15
+ <span>
16
+ <%= link_to "Cancel", :back, class: "inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring inset-ring-gray-300 hover:bg-gray-50" %>
17
+ </span>
18
+
19
+ <span>
20
+ <%= f.submit "Save", class: "inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %>
21
+ </span>
22
+ </div>
23
+ </div>
167
24
 
168
- <div class="form-help">
169
- Default value: <%= @setting.definition.default.inspect %>
25
+ <div class="px-4 py-6 sm:p-6">
26
+ <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
27
+ <% case @setting %>
28
+ <% when Fino::Settings::Boolean %>
29
+ <div class="col-span-full">
30
+ <div class="flex gap-3">
31
+ <div class="flex h-6 shrink-0 items-center">
32
+ <div class="group grid size-4 grid-cols-1">
33
+ <%= f.check_box :value,
34
+ {
35
+ id: "value",
36
+ class: "col-start-1 row-start-1 appearance-none rounded-sm border border-gray-300 bg-white checked:border-indigo-600 checked:bg-indigo-600 indeterminate:border-indigo-600 indeterminate:bg-indigo-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:border-gray-300 disabled:bg-gray-100 disabled:checked:bg-gray-100 forced-colors:appearance-auto",
37
+ checked: @setting.value
38
+ },
39
+ '1',
40
+ '0' %>
41
+ <svg viewBox="0 0 14 14" fill="none" class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-disabled:stroke-gray-950/25">
42
+ <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-checked:opacity-100" />
43
+ <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-indeterminate:opacity-100" />
44
+ </svg>
45
+ </div>
46
+ </div>
47
+ <div class="text-sm/6">
48
+ <label for="value" class="font-medium text-gray-900"><%= @setting.name %></label>
49
+ <p id="comments-description" class="text-gray-500"><%= @setting.description %></p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ <% when Fino::Settings::String %>
54
+ <div class="col-span-full">
55
+ <div>
56
+ <label for="username" class="block text-sm/6 font-medium text-gray-900"><%= @setting.name %></label>
57
+ <div class="mt-2">
58
+ <div class="flex items-center rounded-md bg-white pl-3 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-indigo-600">
59
+ <%= f.text_field :value,
60
+ value: @setting.value,
61
+ class: "block min-w-0 grow bg-white py-1.5 pr-3 pl-1 text-base text-gray-900 placeholder:text-gray-400 focus:outline-none sm:text-sm/6" %>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <p class="mt-3 text-sm/6 text-gray-600"><%= @setting.description %></p>
66
+ </div>
67
+ <% when Fino::Settings::Integer %>
68
+ <div class="col-span-full">
69
+ <div>
70
+ <label for="username" class="block text-sm/6 font-medium text-gray-900"><%= @setting.name %></label>
71
+ <div class="mt-2">
72
+ <div class="flex items-center rounded-md bg-white pl-3 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-indigo-600">
73
+ <%= f.number_field :value,
74
+ value: @setting.value,
75
+ class: "block min-w-0 grow bg-white py-1.5 pr-3 pl-1 text-base text-gray-900 placeholder:text-gray-400 focus:outline-none sm:text-sm/6",
76
+ step: 1 %>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ <p class="mt-3 text-sm/6 text-gray-600"><%= @setting.description %></p>
81
+ </div>
82
+ <% when Fino::Settings::Float %>
83
+ <div class="col-span-full">
84
+ <div>
85
+ <label for="username" class="block text-sm/6 font-medium text-gray-900"><%= @setting.name %></label>
86
+ <div class="mt-2">
87
+ <div class="flex items-center rounded-md bg-white pl-3 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-indigo-600">
88
+ <%= f.number_field :value,
89
+ value: @setting.value,
90
+ class:"block min-w-0 grow bg-white py-1.5 pr-3 pl-1 text-base text-gray-900 placeholder:text-gray-400 focus:outline-none sm:text-sm/6",
91
+ step: 0.1 %>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <p class="mt-3 text-sm/6 text-gray-600"><%= @setting.description %></p>
96
+ </div>
97
+ <% end %>
98
+ </div>
170
99
  </div>
171
- </div>
100
+ <% end %>
172
101
 
173
- <div class="button-group">
174
- <%= f.submit "Update Setting", class: "btn btn-primary" %>
175
- <%= link_to "Cancel", root_path, class: "btn btn-secondary" %>
102
+ <div class="border-t border-gray-100">
103
+ <dl class="divide-y divide-gray-100">
104
+ <div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
105
+ <dt class="text-sm font-medium text-gray-900">Type</dt>
106
+ <dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-1 sm:mt-0 flex"><%= setting_type_label(@setting) %></dd>
107
+ </div>
108
+ <div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
109
+ <dt class="text-sm font-medium text-gray-900">Default</dt>
110
+ <dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0"><%= @setting.definition.default.inspect %></dd>
111
+ </div>
112
+ </dl>
176
113
  </div>
177
- <% end %>
114
+ </div>
178
115
  </div>
179
116
  </div>
@@ -1,138 +1,26 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Fino - <%= yield(:title) || 'Settings' %></title>
7
- <style>
8
- * {
9
- box-sizing: border-box;
10
- margin: 0;
11
- padding: 0;
12
- }
13
2
 
14
- body {
15
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
16
- line-height: 1.6;
17
- color: #0f172a;
18
- background-color: #ffffff;
19
- min-height: 100vh;
20
- }
3
+ <html lang="en" class="h-full bg-white">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
7
 
22
- .container {
23
- max-width: 1200px;
24
- margin: 0 auto;
25
- padding: 2rem 1rem;
26
- }
8
+ <title><%= yield(:title) || 'Settings' %> | Fino</title>
27
9
 
28
- .header {
29
- margin-bottom: 2rem;
30
- }
10
+ <link href="<%= fino_asset_path("/fino.css") %>" rel="stylesheet">
31
11
 
32
- .header h1 {
33
- font-size: 2rem;
34
- font-weight: 600;
35
- color: #0f172a;
36
- margin-bottom: 0.5rem;
37
- }
12
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
13
+ </head>
38
14
 
39
- .header p {
40
- color: #64748b;
41
- font-size: 1rem;
42
- }
15
+ <body class="h-full">
16
+ <%= render "fino/rails/common/sidebar" %>
43
17
 
44
- .card {
45
- background: #ffffff;
46
- border-radius: 8px;
47
- border: 1px solid #e2e8f0;
48
- }
18
+ <div class="xl:pl-72">
19
+ <%= render "fino/rails/common/navbar" %>
49
20
 
50
- .card-header {
51
- padding: 1.5rem;
52
- border-bottom: 1px solid #e2e8f0;
53
- }
54
-
55
- .card-title {
56
- font-size: 1.125rem;
57
- font-weight: 600;
58
- color: #0f172a;
59
- }
60
-
61
- .card-content {
62
- padding: 1.5rem;
63
- }
64
-
65
- .btn {
66
- display: inline-flex;
67
- align-items: center;
68
- justify-content: center;
69
- border-radius: 6px;
70
- font-size: 0.875rem;
71
- font-weight: 500;
72
- transition: colors 0.15s ease-in-out;
73
- cursor: pointer;
74
- text-decoration: none;
75
- border: 1px solid transparent;
76
- }
77
-
78
- .btn-primary {
79
- background-color: #0f172a;
80
- color: #ffffff;
81
- padding: 0.5rem 1rem;
82
- }
83
-
84
- .btn-primary:hover {
85
- background-color: #1e293b;
86
- }
87
-
88
- .btn-secondary {
89
- background-color: transparent;
90
- color: #0f172a;
91
- border-color: #e2e8f0;
92
- padding: 0.5rem 1rem;
93
- }
94
-
95
- .btn-secondary:hover {
96
- background-color: #f8fafc;
97
- }
98
-
99
- .btn-ghost {
100
- background-color: transparent;
101
- color: #0f172a;
102
- padding: 0.5rem;
103
- }
104
-
105
- .btn-ghost:hover {
106
- background-color: #f8fafc;
107
- }
108
-
109
- .text-muted {
110
- color: #64748b;
111
- }
112
-
113
- .text-sm {
114
- font-size: 0.875rem;
115
- }
116
-
117
- .text-xs {
118
- font-size: 0.75rem;
119
- }
120
-
121
- @media (max-width: 640px) {
122
- .container {
123
- padding: 1rem 0.5rem;
124
- }
125
-
126
- .header h1 {
127
- font-size: 1.5rem;
128
- }
129
- }
130
- </style>
131
- <%= yield(:head) %>
132
- </head>
133
- <body>
134
- <div class="container">
135
- <%= yield %>
136
- </div>
137
- </body>
138
- </html>
21
+ <main>
22
+ <%= yield %>
23
+ </main>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Fino::Rails::Engine.routes.draw do
4
- root to: "settings#index"
4
+ root to: "dashboard#index"
5
5
 
6
- get "settings/*key", to: "settings#edit", as: :edit_setting
7
- put "settings/*key", to: "settings#update", as: :update_setting
6
+ get ":section", to: "sections#show", as: :settings_section
7
+
8
+ get ":section/:setting", to: "settings#edit", as: :edit_setting
9
+ put ":section/:setting", to: "settings#update", as: :update_setting
8
10
  end
@@ -9,20 +9,30 @@ class Fino::Rails::Engine < Rails::Engine
9
9
  # Engine
10
10
  #
11
11
 
12
- paths["app"] << root.join("lib", "fino", "rails", "app")
13
- paths["config/initializers"] << root.join("lib", "fino", "rails", "config", "initializers")
12
+ gem_root = root.join("lib", "fino", "rails")
13
+
14
+ paths["app"] << gem_root.join("app")
15
+ paths["config/initializers"] << gem_root.join("config", "initializers")
14
16
 
15
17
  initializer "fino.rails.engine.views" do |_app|
16
18
  ActiveSupport.on_load :action_controller do
17
- prepend_view_path Fino::Rails::Engine.root.join("lib", "fino", "rails", "app", "views")
19
+ prepend_view_path gem_root.join("app", "views")
18
20
  end
19
21
  end
20
22
 
21
23
  initializer "fino.rails.engine.routes", before: :add_routing_paths do |app|
22
- custom_routes = root.join("lib", "fino", "rails", "config", "routes.rb")
24
+ custom_routes = gem_root.join("config", "routes.rb")
23
25
  app.routes_reloader.paths << custom_routes.to_s
24
26
  end
25
27
 
28
+ initializer "fino.rails.engine.assets" do
29
+ config.app_middleware.use(
30
+ Rack::Static,
31
+ urls: ["/fino-assets"],
32
+ root: gem_root.join("public").to_s
33
+ )
34
+ end
35
+
26
36
  #
27
37
  # Configuration
28
38
  #
@@ -30,7 +40,9 @@ class Fino::Rails::Engine < Rails::Engine
30
40
  config.before_configuration do
31
41
  config.fino = ActiveSupport::OrderedOptions.new.update(
32
42
  instrument: Rails.env.development?,
33
- log: Rails.env.development?
43
+ log: Rails.env.development?,
44
+ cache_within_request: true,
45
+ preload_before_request: false
34
46
  )
35
47
  end
36
48
 
@@ -55,12 +67,20 @@ class Fino::Rails::Engine < Rails::Engine
55
67
  end
56
68
  end
57
69
 
58
- use Fino::Rails::RequestScopedCache::Pipe if defined?(Rails::Server)
70
+ use Fino::Rails::RequestScopedCache::Pipe if defined?(Rails::Server) && config.cache_within_request
59
71
  end
60
72
  end
61
73
  end
62
74
 
63
75
  initializer "fino.request_scoped_caching.middleware" do |app|
64
- app.middleware.use Fino::Rails::RequestScopedCache::Middleware if defined?(Rails::Server)
76
+ config = app.config.fino
77
+
78
+ if defined?(Rails::Server)
79
+ app.middleware.use Fino::Rails::RequestScopedCache::Middleware if config.cache_within_request
80
+
81
+ if config.preload_before_request
82
+ app.middleware.use Fino::Rails::Preloading::Middleware, preload: config.preload_before_request
83
+ end
84
+ end
65
85
  end
66
86
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fino::Rails::Preloading::Middleware
4
+ def initialize(app, options = {})
5
+ @app = app
6
+ @preload = options[:preload]
7
+ end
8
+
9
+ def call(env)
10
+ maybe_preload_settings(env) rescue nil # rubocop:disable Style/RescueModifier
11
+
12
+ app.call(env)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :app, :preload
18
+
19
+ def maybe_preload_settings(env) # rubocop:disable Metrics/MethodLength
20
+ request = Rack::Request.new(env)
21
+
22
+ preload_result =
23
+ if preload.respond_to?(:call)
24
+ preload.call(request)
25
+ else
26
+ preload
27
+ end
28
+
29
+ case preload_result
30
+ when TrueClass
31
+ Fino.logger.debug { "Preloading all settings" }
32
+ Fino.settings
33
+ when Hash
34
+ Fino.logger.debug { "Preloading settings: #{preload_result.inspect}" }
35
+ Fino.slice(preload_result)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-500:oklch(63.7% .237 25.331);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-700:oklch(55.4% .135 66.442);--color-green-100:oklch(96.2% .044 156.743);--color-green-500:oklch(72.3% .219 149.579);--color-blue-50:oklch(97% .014 254.604);--color-blue-700:oklch(48.8% .243 264.376);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-700:oklch(49.6% .265 301.924);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-700:oklch(52.5% .223 3.958);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-white:#fff;--spacing:.25rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.sticky{position:sticky}.end-0{inset-inline-end:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.z-40{z-index:40}.col-span-1{grid-column:span 1/span 1}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.row-start-1{grid-row-start:1}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-m-2\.5{margin:calc(var(--spacing)*-2.5)}.m-3{margin:calc(var(--spacing)*3)}.-mx-2{margin-inline:calc(var(--spacing)*-2)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-5{margin-top:calc(var(--spacing)*5)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-full{width:100%;height:100%}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-16{height:calc(var(--spacing)*16)}.h-auto{height:auto}.h-full{height:100%}.h-max{height:max-content}.h-px{height:1px}.max-h-2\.5{max-height:calc(var(--spacing)*2.5)}.max-h-3{max-height:calc(var(--spacing)*3)}.max-h-4{max-height:calc(var(--spacing)*4)}.max-h-5{max-height:calc(var(--spacing)*5)}.max-h-6{max-height:calc(var(--spacing)*6)}.max-h-7{max-height:calc(var(--spacing)*7)}.max-h-10{max-height:calc(var(--spacing)*10)}.max-h-20{max-height:calc(var(--spacing)*20)}.max-h-full{max-height:100%}.max-h-max{max-height:max-content}.min-h-5{min-height:calc(var(--spacing)*5)}.min-h-32{min-height:calc(var(--spacing)*32)}.w-9{width:calc(var(--spacing)*9)}.w-auto{width:auto}.w-full{width:100%}.w-lg{width:var(--container-lg)}.w-xl{width:var(--container-xl)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-lg{max-width:var(--container-lg)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-max{min-width:max-content}.flex-1{flex:1}.flex-2{flex:2}.flex-none{flex:none}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-items-center{justify-items:center}.justify-items-center-safe{justify-items:safe center}.gap-1{gap:calc(var(--spacing)*1)}.gap-3{gap:calc(var(--spacing)*3)}.gap-10{gap:calc(var(--spacing)*10)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-x-3{column-gap:calc(var(--spacing)*3)}.gap-x-4{column-gap:calc(var(--spacing)*4)}.gap-x-6{column-gap:calc(var(--spacing)*6)}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*6)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-5{row-gap:calc(var(--spacing)*5)}.gap-y-7{row-gap:calc(var(--spacing)*7)}.gap-y-8{row-gap:calc(var(--spacing)*8)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.self-center{align-self:center}.self-stretch{align-self:stretch}.justify-self-center{justify-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border,.border-1{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-current{background-color:currentColor}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-green-100{background-color:var(--color-green-100)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.stroke-white{stroke:var(--color-white)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-2\.5{padding:calc(var(--spacing)*2.5)}.p-3{padding:calc(var(--spacing)*3)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.px-0{padding-inline:calc(var(--spacing)*0)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-50{padding-inline:calc(var(--spacing)*50)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-6{padding-block:calc(var(--spacing)*6)}.pt-10{padding-top:calc(var(--spacing)*10)}.pr-3{padding-right:calc(var(--spacing)*3)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-8{padding-left:calc(var(--spacing)*8)}.text-center{text-align:center}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-sm\/6{font-size:var(--text-sm);line-height:calc(var(--spacing)*6)}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-xs\/5{font-size:var(--text-xs);line-height:calc(var(--spacing)*5)}.text-xs\/6{font-size:var(--text-xs);line-height:calc(var(--spacing)*6)}.text-\[0\.625rem\]{font-size:.625rem}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.text-blue-700{color:var(--color-blue-700)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-indigo-600{color:var(--color-indigo-600)}.text-pink-700{color:var(--color-pink-700)}.text-purple-700{color:var(--color-purple-700)}.text-red-500{color:var(--color-red-500)}.text-white{color:var(--color-white)}.text-yellow-700{color:var(--color-yellow-700)}.opacity-0{opacity:0}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-ring{--tw-inset-ring-shadow:inset 0 0 0 1px var(--tw-inset-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.inset-ring-blue-700\/10{--tw-inset-ring-color:#1447e61a}@supports (color:color-mix(in lab, red, red)){.inset-ring-blue-700\/10{--tw-inset-ring-color:color-mix(in oklab,var(--color-blue-700)10%,transparent)}}.inset-ring-gray-300{--tw-inset-ring-color:var(--color-gray-300)}.inset-ring-gray-700\/10{--tw-inset-ring-color:#3641531a}@supports (color:color-mix(in lab, red, red)){.inset-ring-gray-700\/10{--tw-inset-ring-color:color-mix(in oklab,var(--color-gray-700)10%,transparent)}}.inset-ring-pink-700\/10{--tw-inset-ring-color:#c4005c1a}@supports (color:color-mix(in lab, red, red)){.inset-ring-pink-700\/10{--tw-inset-ring-color:color-mix(in oklab,var(--color-pink-700)10%,transparent)}}.inset-ring-purple-700\/10{--tw-inset-ring-color:#8200da1a}@supports (color:color-mix(in lab, red, red)){.inset-ring-purple-700\/10{--tw-inset-ring-color:color-mix(in oklab,var(--color-purple-700)10%,transparent)}}.inset-ring-yellow-700\/10{--tw-inset-ring-color:#a361001a}@supports (color:color-mix(in lab, red, red)){.inset-ring-yellow-700\/10{--tw-inset-ring-color:color-mix(in oklab,var(--color-yellow-700)10%,transparent)}}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline,.outline-1{outline-style:var(--tw-outline-style);outline-width:1px}.-outline-offset-1{outline-offset:calc(1px*-1)}.outline-gray-200{outline-color:var(--color-gray-200)}.outline-gray-300{outline-color:var(--color-gray-300)}.outline-gray-900\/5{outline-color:#1018280d}@supports (color:color-mix(in lab, red, red)){.outline-gray-900\/5{outline-color:color-mix(in oklab,var(--color-gray-900)5%,transparent)}}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.group-hover\:border-indigo-600:is(:where(.group):hover *){border-color:var(--color-indigo-600)}.group-hover\:text-indigo-600:is(:where(.group):hover *){color:var(--color-indigo-600)}}.group-has-checked\:opacity-100:is(:where(.group):has(:checked) *),.group-has-indeterminate\:opacity-100:is(:where(.group):has(:indeterminate) *){opacity:1}.group-has-disabled\:stroke-gray-950\/25:is(:where(.group):has(:disabled) *){stroke:#03071240}@supports (color:color-mix(in lab, red, red)){.group-has-disabled\:stroke-gray-950\/25:is(:where(.group):has(:disabled) *){stroke:color-mix(in oklab,var(--color-gray-950)25%,transparent)}}.placeholder\:text-gray-400::placeholder{color:var(--color-gray-400)}.checked\:border-indigo-600:checked{border-color:var(--color-indigo-600)}.checked\:bg-indigo-600:checked{background-color:var(--color-indigo-600)}.indeterminate\:border-indigo-600:indeterminate{border-color:var(--color-indigo-600)}.indeterminate\:bg-indigo-600:indeterminate{background-color:var(--color-indigo-600)}.focus-within\:outline-2:focus-within{outline-style:var(--tw-outline-style);outline-width:2px}.focus-within\:-outline-offset-2:focus-within{outline-offset:calc(2px*-1)}.focus-within\:outline-indigo-600:focus-within{outline-color:var(--color-indigo-600)}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:text-indigo-600:hover{color:var(--color-indigo-600)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-indigo-600:focus-visible{outline-color:var(--color-indigo-600)}.disabled\:border-gray-300:disabled{border-color:var(--color-gray-300)}.disabled\:bg-gray-100:disabled,.disabled\:checked\:bg-gray-100:disabled:checked{background-color:var(--color-gray-100)}@media (min-width:40rem){.sm\:col-span-1{grid-column:span 1/span 1}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:mt-0{margin-top:calc(var(--spacing)*0)}.sm\:ml-3{margin-left:calc(var(--spacing)*3)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:gap-4{gap:calc(var(--spacing)*4)}.sm\:rounded-xl{border-radius:var(--radius-xl)}.sm\:p-5{padding:calc(var(--spacing)*5)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:text-sm\/6{font-size:var(--text-sm);line-height:calc(var(--spacing)*6)}}@media (min-width:48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:w-xl{width:var(--container-xl)}.md\:justify-between{justify-content:space-between}.md\:p-5{padding:calc(var(--spacing)*5)}.md\:px-50{padding-inline:calc(var(--spacing)*50)}}@media (min-width:64rem){.lg\:mt-0{margin-top:calc(var(--spacing)*0)}.lg\:ml-4{margin-left:calc(var(--spacing)*4)}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:gap-x-6{column-gap:calc(var(--spacing)*6)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:px-20{padding-inline:calc(var(--spacing)*20)}.lg\:px-50{padding-inline:calc(var(--spacing)*50)}}@media (min-width:80rem){.xl\:fixed{position:fixed}.xl\:inset-y-0{inset-block:calc(var(--spacing)*0)}.xl\:z-50{z-index:50}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:w-72{width:calc(var(--spacing)*72)}.xl\:flex-col{flex-direction:column}.xl\:px-30{padding-inline:calc(var(--spacing)*30)}.xl\:px-50{padding-inline:calc(var(--spacing)*50)}.xl\:pl-72{padding-left:calc(var(--spacing)*72)}}@media (min-width:96rem){.\32 xl\:px-100{padding-inline:calc(var(--spacing)*100)}}@media (forced-colors:active){.forced-colors\:appearance-auto{appearance:auto}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ viewBox="0 0 1147.6376 283.26123"
4
+ version="1.1"
5
+ id="svg1"
6
+ sodipodi:docname="logo_new.svg"
7
+ width="1147.6376"
8
+ height="283.26123"
9
+ inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs1" />
16
+ <sodipodi:namedview
17
+ id="namedview1"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#000000"
20
+ borderopacity="0.25"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:zoom="0.54166667"
26
+ inkscape:cx="565.84615"
27
+ inkscape:cy="167.07692"
28
+ inkscape:window-width="2560"
29
+ inkscape:window-height="1302"
30
+ inkscape:window-x="0"
31
+ inkscape:window-y="25"
32
+ inkscape:window-maximized="1"
33
+ inkscape:current-layer="svg1" />
34
+ <path
35
+ fill="#4f39f6"
36
+ d="m 142.05593,2.3655083 c 0.72,0 1.441,-0.003 2.184,-0.004 2.405,-0.004 4.81,0 7.215,0.003 q 2.619,-10e-4 5.238,-0.005 7.185,-0.005 14.37,10e-4 7.751,0.003 15.504,-0.004 15.18,-0.005 30.36,0 12.33,0.003 24.661,0.001 h 10.671 a 186077,186077 0 0 1 66.827,0.004 q 28.695,0.007 57.39,0 33.307,-0.01 66.615,-0.005 l 7.096,0.001 h 3.526 q 12.326,10e-4 24.653,-0.002 15.006,-0.005 30.012,0.003 7.662,0.004 15.325,0 7.004,-0.003 14.007,0.004 2.543,0.002 5.086,-0.002 3.438,-0.003 6.875,0.005 h 3.87 c 2.945,0.126 2.945,0.126 4.945,1.126 -0.666,6.7749997 -2.173,13.1759997 -3.87,19.7619997 -0.284,1.108 -0.566,2.217 -0.857,3.36 q -0.893,3.499 -1.793,6.996 a 3995,3995 0 0 0 -2.732,10.704 q -0.87,3.408 -1.744,6.815 l -0.82,3.226 -0.78,3.015 -0.677,2.638 c -1.97,6.726 -1.97,6.726 -3.727,8.484 a 79,79 0 0 1 -4.828,0.12 l -3.166,0.004 -3.503,-0.01 h -3.665 q -4.99,-0.001 -9.979,-0.013 -5.209,-0.008 -10.418,-0.008 -9.87,-0.005 -19.74,-0.02 -11.235,-0.017 -22.47,-0.023 a 59370,59370 0 0 1 -46.23,-0.05 c -0.133,0.741 -0.265,1.483 -0.4,2.247 l -0.535,2.97 -0.524,2.935 c -0.753,3.954 -1.722,7.834 -2.742,11.727 l -1.224,4.787 q -0.954,3.704 -1.916,7.406002 c -0.626,2.413 -1.243,4.83 -1.86,7.244 -0.195,0.743 -0.39,1.485 -0.593,2.251 -1.156,3.778 -1.156,3.778 -0.207,7.433 0.856,-0.07 1.712,-0.14 2.595,-0.214 10.651,-0.803 21.243,-0.933 31.92,-0.919 q 2.75,0 5.497,-0.003 5.708,0 11.416,0.005 7.32,0.007 14.637,-0.003 5.651,-0.005 11.303,0 2.701,10e-4 5.402,-0.002 3.753,-0.003 7.508,0.006 l 2.248,-0.006 c 3.1,0.013 5.508,0.147 8.474,1.136 -0.605,6.928 -1.94,13.4 -3.688,20.125 l -0.795,3.115 a 2354,2354 0 0 1 -1.665,6.464 q -1.282,4.968 -2.548,9.94 l -1.617,6.293 -0.77,3.015 -0.718,2.763 -0.63,2.441 c -0.569,1.844 -0.569,1.844 -1.569,2.844 -3.166,0.095 -6.31,0.125 -9.477,0.113 l -3.015,0.001 c -3.306,0 -6.612,-0.009 -9.918,-0.017 l -6.857,-0.004 q -9.043,-0.007 -18.085,-0.024 -9.22,-0.014 -18.439,-0.02 -18.105,-0.016 -36.209,-0.049 l -0.557,2.664 c -1.606,7.598 -3.332,15.143 -5.264,22.664 l -0.788,3.095 q -1.255,4.914 -2.516,9.827 l -0.868,3.39 a 14273,14273 0 0 1 -6.007,23.36 l -0.747,2.898 q -1.922,7.464 -3.87,14.92 a 2121,2121 0 0 0 -1.128,4.337 c -0.51,1.971 -1.029,3.94 -1.548,5.907 l -0.86,3.285 c -0.847,2.653 -0.847,2.653 -2.847,5.653 -2.969,0.43 -2.969,0.43 -6.96,0.507 l -2.288,0.054 c -2.635,0.055 -5.269,0.087 -7.905,0.12 q -2.943,0.048 -5.888,0.1 c -33.543,0.515 -67.1,0.368 -100.648,0.347 q -10.332,-0.004 -20.665,0.002 c -74.462,0.044 -74.462,0.044 -104.512,-0.402 q -3.301,-0.048 -6.602,-0.08 a 1116,1116 0 0 1 -8.778,-0.14 l -2.517003,-0.019 c -4.577,-0.117 -7.594,-0.655 -11.237,-3.489 -4.179,-4.759 -5.323,-7.468 -5.203,-13.688 0.305,-3.48 1.143,-5.48 3.203,-8.312 5.471,-4.513 10.118,-5.727 17.086003,-5.6 1.321,-0.008 1.321,-0.008 2.67,-0.014 2.932,-0.009 5.863,0.02 8.796,0.046 q 3.165,0.003 6.33,-0.002 c 5.713,0 11.427,0.028 17.142,0.063 5.976,0.031 11.952,0.034 17.93,0.04 11.311,0.016 22.622,0.057 33.934,0.107 12.88,0.056 25.76,0.083 38.64,0.108 26.491,0.052 52.981,0.14 79.472,0.252 l 0.308,-2.723 c 0.81,-5.666 2.337,-11.176 3.754,-16.715 l 0.854,-3.373 q 1.037,-4.095 2.084,-8.189 l -1.874,0.001 a 335971,335971 0 0 1 -123.187,0.054 l -2.657,10e-4 q -21.28,0.007 -42.562,0.021 -21.84,0.014 -43.684,0.017 -13.473003,0.002 -26.947003,0.014 -9.245,0.008 -18.488,0.005 -5.33,0 -10.66,0.006 -5.79,0.007 -11.577,0.001 l -3.379,0.01 c -19.357,-0.038 -19.357,-0.038 -24.9849995,-5.13 -3.19,-3.928 -4.73600002,-7.763 -4.45300002,-12.945 1.11100002,-5.039 3.95200002,-8.92 8.01500002,-11.993 4.0069995,-1.746 7.3899995,-2.187 11.7559995,-2.185 l 3.285,-0.006 3.61,0.01 3.833,-0.004 q 5.271,-0.002 10.542,0.007 5.682,0.005 11.366,0.002 9.847,0 19.694,0.008 14.238,0.011 28.475003,0.012 23.1,0.004 46.197,0.018 a 111901,111901 0 0 0 61.55,0.026 q 57.564,0.016 115.13,0.05 l 0.345,-2.066 a 173,173 0 0 1 4.28,-18.622 c 0.23,-0.809 0.46,-1.617 0.695,-2.45 q 0.834,-2.933 1.68,-5.862 l -3.414,0.005 q -41.112,0.068 -82.225,0.098 -19.882,0.015 -39.764,0.048 -17.329,0.03 -34.657,0.037 -9.176,0.004 -18.35,0.023 -8.64,0.02 -17.277,0.015 -3.169,0.001 -6.337,0.012 c -2.888,0.01 -5.775,0.008 -8.662,0.002 l -2.524,0.018 c -6.285,-0.032 -12.01,-0.714 -17.603003,-3.82 -3.502,-3.903 -5.315,-8.323 -5.097,-13.626 0.97,-4.878 2.997,-7.644 6.910003,-10.812 2.813,-1.647 4.843,-2.25 8.094,-2.259 l 2.46,-0.02 2.683,0.007 2.864,-0.015 c 3.176,-0.015 6.351,-0.016 9.526,-0.016 l 6.82,-0.026 q 7.347,-0.027 14.695,-0.037 c 7.738,-0.012 15.477,-0.037 23.215,-0.065 q 16.265,-0.054 32.528,-0.092 l 2.085,-0.005 2.088,-0.005 q 14.658,-0.034 29.317,-0.074 l 2.084,-0.006 q 17.183,-0.049 34.37,-0.105 11.55,-0.038 23.1,-0.06 7.17,-0.016 14.34,-0.039 3.335,-0.009 6.672,-0.013 4.537,-0.007 9.075,-0.025 l 2.698,0.001 c 4.278,0.103 4.278,0.103 8.286,-1.146 0.552,-2.039 0.552,-2.039 0.875,-4.563 1.243,-7.602 3.212,-14.98 5.125,-22.437002 -14.638,-1.044 -29.222,-1.124 -43.892,-1.082 l -7.688,0.01 q -8.223,0.009 -16.446,0.027 -11.9,0.024 -23.799,0.034 -19.33,0.018 -38.66,0.045 l -2.329,0.004 a 71734,71734 0 0 0 -35.136,0.058 l -2.336,0.004 q -19.285,0.034 -38.57,0.047 -11.873,0.01 -23.745,0.04 -9.082,0.021 -18.166003,0.021 -3.723,0.003 -7.446,0.017 c -3.386,0.012 -6.772,0.012 -10.158,0.01 l -2.978,0.018 c -6.46,-0.023 -11.774,-0.488 -17.4,-3.94 -3.223,-3.312 -5.12,-6.622 -5.349,-11.333 0.383,-4.882 1.914,-8.178 5.098,-11.98 4.562,-3.308 8.455,-4.126 14.005,-4.123 l 3.075,-0.006 3.372,0.01 c 1.776,-0.003 1.776,-0.003 3.587,-0.004 q 4.929,0 9.858,0.007 5.314,0.005 10.630003,0.002 9.21,0 18.417,0.008 13.314,0.011 26.63,0.012 21.6,0.005 43.202,0.018 a 97847,97847 0 0 0 44.582,0.022 l 12.976,0.004 q 53.834,0.016 107.666,0.05 l 0.345,-2.066 a 173,173 0 0 1 4.28,-18.622 c 0.23,-0.809 0.46,-1.617 0.695,-2.45 q 0.834,-2.933 1.68,-5.862 l -3.617,-0.005 a 52384,52384 0 0 1 -95.492,-0.203 l -2.105,-0.006 q -16.866,-0.044 -33.73,-0.059 -17.31,-0.02 -34.619,-0.082 c -6.48,-0.022 -12.96,-0.038 -19.44,-0.037 q -9.15,0 -18.3,-0.049 -3.358,-0.013 -6.715,-0.005 c -3.059,0.007 -6.116,-0.01 -9.175,-0.035 l -2.676,0.023 c -5.826,-0.08 -10.263,-1.257 -15.13,-4.542 -3.324,-3.489 -4.273,-6.236 -4.5,-10.938 0.238,-5.132 1.29,-8.03 4.5,-12.0619997 6.982,-4.029 14.688,-4.125 22.568,-4.126 m 545.931,-0.284 3.338,0.032 3.592,0.101 3.707,0.062 q 3.88,0.075 7.76,0.176 c 3.956,0.101 7.91,0.165 11.865,0.224 q 3.772,0.078 7.543,0.163 l 3.58,0.057 c 6.97,0.206 12.126,0.594 17.268,5.755 2.346,3.6059997 3.873,7.2709997 5.41,11.2769997 l 1.027,2.621 c 3.966,10.217 7.696,20.523 11.446,30.821 a 3646,3646 0 0 0 9.089,24.621 21495,21495 0 0 1 18.228,49.220002 l 1.069,2.895 1.979,5.36 q 0.881,2.387 1.768,4.772 a 369,369 0 0 1 1.648,4.557 c 1.476,4.214 1.476,4.214 4.183,7.696 0.625,2.625 0.625,2.625 1,5 h 1 l 0.563,-2.28 1.722,-6.986 2.084,-8.45 5.32,-21.576 3.12,-12.638 0.64,-2.598 a 19095,19095 0 0 0 9.023,-36.734002 q 1.842,-7.522 3.688,-15.045 1.765,-7.203 3.522,-14.409 1.097,-4.496 2.203,-8.99 0.51,-2.076 1.013,-4.15 c 1.672,-6.895 3.412,-13.574 6.102,-20.1439997 13.555,-0.934 27.042,-1.123 40.625,-1.063 l 5.969,0.014 q 7.203,0.017 14.406,0.05 c 1.3,4.484 0.61,7.6679997 -0.518,12.1549997 l -0.532,2.153 c -0.592,2.38 -1.198,4.757 -1.805,7.134 l -1.281,5.13 a 4202,4202 0 0 1 -3.524,13.981 c -1.25,4.941 -2.487,9.885 -3.725,14.83 q -3.552,14.156 -7.125,28.308 a 25786,25786 0 0 0 -7.908,31.436002 54705,54705 0 0 1 -9.97,39.652 l -0.752,2.99 -1.492,5.924 a 21653,21653 0 0 0 -11.836,47.178 q -2.351,9.412 -4.695,18.824 c -2.855,11.471 -5.711,22.942 -8.7,34.38 l -0.618,2.38 c -1.398,5.303 -1.398,5.303 -2.52,7.544 -4.162,1.234 -8.388,1.145 -12.694,1.133 l -2.247,0.004 c -2.394,0.003 -4.788,-0.004 -7.183,-0.012 l -2.457,-0.007 c -19.39,-0.062 -19.39,-0.062 -27.855,-0.368 l -2.194,-0.043 c -3.104,-0.153 -5.318,-0.68 -8.08,-2.12 -3.361,-3.799 -4.764,-7.983 -6.476,-12.712 l -2.332,-6.207 -1.217,-3.288 a 763,763 0 0 0 -3.963,-10.329 l -1.353,-3.47 q -1.29,-3.312 -2.585,-6.623 c -2.181,-5.646 -4.105,-11.225 -5.576,-17.1 l -0.787,-1.858 -3,-1 h 2 l -0.983,-2.51 c -8.765,-22.4 -17.366,-44.86 -25.881,-67.357 l -1.214,-3.203 -2.27,-6 -1.016,-2.684 -0.888,-2.35 c -0.732,-1.939 -0.732,-1.939 -1.748,-3.896 -4.835,14.233 -8.267,28.813 -11.804,43.408 -1.395,5.746 -2.804,11.49 -4.21,17.233 l -1.64,6.713 c -3.846,15.732 -7.762,31.447 -11.692,47.158 l -1.578,6.316 q -1.47,5.888 -2.943,11.773 c -0.29,1.158 -0.577,2.315 -0.875,3.508 -1.335,5.325 -2.704,10.626 -4.258,15.891 q -12.074,0.231 -24.147,0.443 -5.607,0.098 -11.212,0.205 -5.408,0.104 -10.815,0.195 -2.067,0.036 -4.132,0.077 c -1.925,0.04 -3.85,0.07 -5.775,0.102 l -3.324,0.06 c -2.595,-0.082 -2.595,-0.082 -4.595,-1.082 0.638,-8.088 2.503,-15.744 4.473,-23.598 l 1.074,-4.342 q 1.448,-5.844 2.908,-11.686 1.538,-6.171 3.066,-12.345 c 2.289,-9.224 4.589,-18.446 6.89,-27.668 q 1.376,-5.514 2.75,-11.029 a 34186,34186 0 0 1 10.887,-43.546 q 4,-15.968 7.99,-31.94 2.253,-9.015 4.513,-18.029002 1.236,-4.924 2.469,-9.848 l 0.493,-1.967 a 4140,4140 0 0 0 6.637,-26.909 q 1.8,-7.39 3.616,-14.776 c 0.82,-3.344 1.636,-6.689 2.438,-10.038 0.883,-3.674 1.783,-7.344 2.687,-11.013 l 0.778,-3.299 0.754,-3.02 0.635,-2.6189997 c 2.13,-5.26 4.903,-6.839 10.442,-6.738 M 1118.1029,22.620508 c 17.137,14.975 26.922,33.11 29.07,55.934 q 0.186,2.967 0.313,5.937 l 0.125,2.68 c 0.07,3.118 -0.01,6.204 -0.125,9.32 l -0.056,1.932 c -1.927,52.148002 -20.677,112.533002 -58.944,149.068002 -21.777,19.825 -48.89,30.898 -78,34 -1.243,0.14 -1.243,0.14 -2.512,0.285 -2.786,0.314 -5.574,0.61 -8.36297,0.903 l -2.668,0.29 c -5.399,0.528 -10.096,0.322 -15.457,-0.478 a 605,605 0 0 0 -7.25,-0.312 c -25.032,-1.305 -50.525,-9.134 -68.137,-27.797 -6.638,-7.781 -14.613,-18.351 -14.613,-28.891 l -2,-1 c -0.486,-3 -0.85,-5.918 -1.125,-8.937 l -0.253,-2.65 c -1.604,-17.56 1.018,-34.257 4.94,-51.35 l 0.522,-2.308 c 11.206,-49.485 11.206,-49.485 19.916,-69.755002 l 1.014,-2.373 c 6.904,-15.895 15.77,-29.46 26.986,-42.627 q 1.283,-1.624 2.562,-3.25 c 39.167,-44.1879997 127.26597,-56.681 174.05497,-18.621 m -128.61697,59.87 -1.352,1.337 c -13.537,14.26 -17.793,35.237002 -22.133,53.750002 a 1414,1414 0 0 1 -1.94,8.135 c -8.14,31.087 -8.14,31.087 -1.114,61.455 5.378,8.12 13.633,12.225 22.851,14.574 13.367,2.294 27.03497,1.694 39.68797,-3.25 l 2.726,-1.062 c 6.857,-3.057 11.99,-7.681 17.274,-12.938 l 1.867,-1.762 c 18.356,-18.821 21.737,-49.357 27.277,-73.824 0.173,-0.76 0.346,-1.517 0.523,-2.3 3.53,-15.704 5.04,-32.853002 -2.917,-47.400002 -5.68,-8.634 -15.02,-12.427 -24.75,-14.714 l -2.875,-0.687 c -21.04,-2.104 -40.516,2.894 -55.12497,18.687 M 587.39393,2.3615083 c 0.999,-0.002 1.998,-0.005 3.027,-0.01 l 3.276,0.008 3.39,-0.003 q 3.565,-0.002 7.128,0.005 c 3.597,0.005 7.194,0 10.79,-0.006 q 3.472,0 6.942,0.004 l 3.215,-0.007 c 7.48,0.023 14.875,0.476 22.324,1.139 1.5,3.937 0.488,6.8859997 -0.574,10.8649997 l -0.54,2.058 a 842,842 0 0 1 -1.173,4.41 1223,1223 0 0 0 -1.873,7.064 q -0.962,3.672 -1.932,7.343 c -2.096,7.938 -4.103,15.897 -6.091,23.862 l -1.04,4.15 -2.661,10.656 -1.705,6.828 c -10.416,41.709002 -20.773,83.434002 -30.887,125.218002 l -0.551,2.275 q -2.606,10.758 -5.196,21.518 c -3.761,15.63 -7.642,31.218 -11.777,46.753 -15.668,0.932 -31.188,0.992 -46.875,0.563 l -6.492,-0.158 q -7.817,-0.191 -15.633,-0.405 c -1.602,-3.205 -0.136,-6.275 0.712,-9.605 l 0.593,-2.4 c 0.659,-2.657 1.328,-5.312 1.996,-7.967 l 1.402,-5.641 q 1.503,-6.05 3.02,-12.1 2.404,-9.601 4.79,-19.208 2.43,-9.765 4.866,-19.53 a 10659,10659 0 0 0 9.819,-39.645 l 2.117,-8.616 c 7.427,-30.237 14.937,-60.453002 22.47,-90.664002 q 1.71,-6.844 3.41,-13.69 2.007,-8.06 4.02,-16.12 0.748,-2.993 1.492,-5.987 1.024,-4.12 2.057,-8.238 l 0.6,-2.432 c 2.028,-8.0469997 2.028,-8.0469997 3.636,-11.1569997 2.942,-0.98 4.843,-1.126 7.908,-1.13"
37
+ id="path1" />
38
+ </svg>
data/lib/fino/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fino
4
- VERSION = "1.0.4"
4
+ VERSION = "1.1.0"
5
5
  REQUIRED_RUBY_VERSION = ">= 3.0.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fino-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Egor Iskrenkov
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 1.0.4
18
+ version: 1.1.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 1.0.4
25
+ version: 1.1.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rails
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -44,15 +44,27 @@ extensions: []
44
44
  extra_rdoc_files: []
45
45
  files:
46
46
  - README.md
47
+ - lib/fino/rails/app/assets/stylesheets/fino.css
47
48
  - lib/fino/rails/app/controllers/fino/rails/application_controller.rb
49
+ - lib/fino/rails/app/controllers/fino/rails/dashboard_controller.rb
50
+ - lib/fino/rails/app/controllers/fino/rails/sections_controller.rb
48
51
  - lib/fino/rails/app/controllers/fino/rails/settings_controller.rb
52
+ - lib/fino/rails/app/helpers/fino/rails/application_helper.rb
53
+ - lib/fino/rails/app/helpers/fino/rails/settings_helper.rb
54
+ - lib/fino/rails/app/views/fino/rails/common/_navbar.html.erb
55
+ - lib/fino/rails/app/views/fino/rails/common/_sidebar.html.erb
56
+ - lib/fino/rails/app/views/fino/rails/dashboard/index.html.erb
57
+ - lib/fino/rails/app/views/fino/rails/sections/show.html.erb
58
+ - lib/fino/rails/app/views/fino/rails/settings/_setting.html.erb
49
59
  - lib/fino/rails/app/views/fino/rails/settings/edit.html.erb
50
- - lib/fino/rails/app/views/fino/rails/settings/index.html.erb
51
60
  - lib/fino/rails/app/views/layouts/fino/rails/application.html.erb
52
61
  - lib/fino/rails/config/routes.rb
53
62
  - lib/fino/rails/engine.rb
54
63
  - lib/fino/rails/instrumentation/log_subscriber.rb
55
64
  - lib/fino/rails/instrumentation/pipe.rb
65
+ - lib/fino/rails/preloading/middleware.rb
66
+ - lib/fino/rails/public/fino-assets/fino.css
67
+ - lib/fino/rails/public/fino-assets/logo.svg
56
68
  - lib/fino/rails/request_scoped_cache/middleware.rb
57
69
  - lib/fino/rails/request_scoped_cache/pipe.rb
58
70
  - lib/fino/version.rb
@@ -1,155 +0,0 @@
1
- <% content_for(:title, 'Application Settings') %>
2
- <% content_for(:head) do %>
3
- <style>
4
- .settings-grid {
5
- display: grid;
6
- grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
7
- gap: 1.5rem;
8
- }
9
-
10
- .setting-item {
11
- display: block;
12
- padding: 1rem;
13
- border-bottom: 1px solid #e2e8f0;
14
- text-decoration: none;
15
- color: inherit;
16
- transition: background-color 0.15s ease-in-out;
17
- }
18
-
19
- .setting-item:hover {
20
- background-color: #f8fafc;
21
- }
22
-
23
- .setting-item:last-child {
24
- border-bottom: none;
25
- }
26
-
27
- .setting-name {
28
- font-weight: 600;
29
- color: #0f172a;
30
- font-size: 0.875rem;
31
- margin-bottom: 0.25rem;
32
- display: flex;
33
- align-items: center;
34
- gap: 0.5rem;
35
- }
36
-
37
- .setting-type {
38
- display: inline-block;
39
- padding: 0.125rem 0.5rem;
40
- background-color: #f1f5f9;
41
- color: #475569;
42
- font-size: 0.75rem;
43
- font-weight: 500;
44
- border-radius: 4px;
45
- text-transform: uppercase;
46
- letter-spacing: 0.025em;
47
- }
48
-
49
- .setting-value {
50
- color: #0f172a;
51
- font-size: 0.875rem;
52
- margin-top: 0.5rem;
53
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;
54
- background-color: #f8fafc;
55
- padding: 0.5rem;
56
- border-radius: 4px;
57
- border: 1px solid #e2e8f0;
58
- font-weight: 500;
59
- }
60
-
61
- .setting-default {
62
- color: #64748b;
63
- font-size: 0.75rem;
64
- margin-top: 0.25rem;
65
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Courier New', monospace;
66
- }
67
-
68
- .setting-description {
69
- color: #64748b;
70
- font-size: 0.875rem;
71
- margin-top: 0.25rem;
72
- font-style: italic;
73
- }
74
-
75
- .empty-state {
76
- text-align: center;
77
- padding: 3rem 1rem;
78
- color: #64748b;
79
- }
80
-
81
- .empty-state svg {
82
- width: 48px;
83
- height: 48px;
84
- margin: 0 auto 1rem;
85
- color: #cbd5e1;
86
- }
87
-
88
- @media (max-width: 640px) {
89
- .settings-grid {
90
- grid-template-columns: 1fr;
91
- }
92
- }
93
- </style>
94
- <% end %>
95
-
96
- <div class="header">
97
- <h1>Application Settings</h1>
98
- <p>Global configuration settings for your application</p>
99
- </div>
100
-
101
- <%
102
- # Group settings by section
103
- grouped_settings = @settings.group_by { |setting| setting.section_name || 'Global' }
104
-
105
- if grouped_settings.empty?
106
- %>
107
- <div class="empty-state">
108
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
109
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
110
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
111
- </svg>
112
- <h3>No settings configured</h3>
113
- <p>Configure settings in your application to see them here.</p>
114
- </div>
115
- <% else %>
116
- <div class="settings-grid">
117
- <%
118
- # Show Global settings first, then other sections
119
- sections_order = grouped_settings.keys.sort_by { |section| section == 'Global' ? 0 : 1 }
120
- sections_order.each do |section_name|
121
- settings = grouped_settings[section_name]
122
- is_global = section_name == 'Global'
123
- %>
124
- <div class="card">
125
- <div class="card-header">
126
- <h2 class="card-title">
127
- <%= is_global ? 'Global Settings' : section_name.to_s.humanize %>
128
- </h2>
129
- </div>
130
- <div class="card-content" style="padding: 0;">
131
- <% settings.each do |setting| %>
132
- <%= link_to edit_setting_path(setting.key), class: "setting-item" do %>
133
- <div class="setting-name">
134
- <%= setting.name %>
135
- <span class="setting-type"><%= setting.class.name.demodulize %></span>
136
- </div>
137
-
138
- <% if setting.definition.options[:description].present? %>
139
- <div class="setting-description"><%= setting.definition.options[:description] %></div>
140
- <% end %>
141
-
142
- <div class="setting-value">
143
- <%= setting.value.inspect %>
144
- </div>
145
-
146
- <div class="setting-default">
147
- Default: <%= setting.default.inspect %>
148
- </div>
149
- <% end %>
150
- <% end %>
151
- </div>
152
- </div>
153
- <% end %>
154
- </div>
155
- <% end %>