one-for-all-framework 4.4.0 → 5.0.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/README.md +13 -2
- data/app/controllers/activity_logs_controller.rb +9 -0
- data/app/controllers/application_controller.rb +9 -0
- data/app/controllers/dashboard_controller.rb +2 -1
- data/app/controllers/pages_controller.rb +4 -0
- data/app/controllers/posts_controller.rb +4 -0
- data/app/middleware/maintenance_middleware.rb +32 -0
- data/app/models/activity_log.rb +26 -0
- data/app/models/user.rb +14 -0
- data/app/views/cms/activity_logs.erb +177 -0
- data/app/views/dashboard.erb +96 -0
- data/app/views/docs.erb +85 -2
- data/app/views/index.erb +1 -1
- data/app/views/layout.erb +9 -0
- data/app/views/maintenance.erb +143 -0
- data/bin/ofa +42 -4
- data/config/boot.rb +13 -0
- data/config/database.rb +12 -0
- data/config/features.json +2 -1
- data/config/routes.rb +4 -0
- data/config.ru +4 -0
- data/db/data.sqlite3 +0 -0
- data/db/migrations/20260502000000_create_products.rb +1 -1
- data/db/migrations/20260507000000_create_activity_logs.rb +13 -0
- data/db/migrations/20260507000001_fix_activity_logs_user_id.rb +7 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 189b80a3eb7da358bd91a56cb6ffb17997f94433ee00fdd0366762320bea5f1e
|
|
4
|
+
data.tar.gz: 5693b52708caad36aa969c58ac3f25ae3b17a1d2e964de44ab7761577050aa5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f1bff39a001463536a39c0ee436438cb8579287cbbc4d1846b439a2172df848cead0a4dbc87ef0046cf0bc359833c47dca33e3347e2cfb94ac3c1108231f26c
|
|
7
|
+
data.tar.gz: 8375e210da774b3fd0f6db508c5a44df418c1d395cd71d742aa10bec261f3429b65b904a9cc07c36d906d35edfa9e1fdd00d6e0916e17143410520ee2ebd44f8
|
data/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
<img src="public/images/logo.png" width="500" height="500" alt="OFA Framework Logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
# ⚡ One-For-All (OFA) Framework
|
|
5
|
+
# ⚡ One-For-All (OFA) Framework v5.0.0
|
|
6
6
|
|
|
7
7
|
[](https://www.ruby-lang.org/)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[]()
|
|
10
10
|
|
|
11
|
-
**One-For-All (OFA)** is a
|
|
11
|
+
**One-For-All (OFA)** is a modern and powerful web application framework designed for developers who value high performance, scalability, and premium aesthetics. Built on the high-performance **Eksa Server** engine, OFA v5.0.0 introduces advanced dashboard analytics, native Lucide icon support, and a robust plugin architecture.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
- **🌐 Global Support**: Multi-language (I18n) support and SEO optimization ready.
|
|
23
23
|
- **🖋️ Rich Text Editor**: Integrated Trix Editor for seamless post and page creation with image upload support.
|
|
24
24
|
- **📡 Modern API**: Built-in JWT support and automated Swagger/OpenAPI documentation.
|
|
25
|
+
- **🚧 Maintenance Mode**: Instantly toggle "Under Construction" mode via CLI.
|
|
26
|
+
- **🔍 Activity Logs**: Full audit trail in CMS to monitor user actions and security.
|
|
27
|
+
- **📊 Dashboard Analytics**: Integrated Chart.js for real-time content and user statistics.
|
|
28
|
+
- **🎨 Lucide Icons**: Native support for modern, lightweight Lucide icon set.
|
|
29
|
+
- **🔌 Plugin System**: Modular architecture to extend the framework with custom add-ons.
|
|
25
30
|
|
|
26
31
|
---
|
|
27
32
|
|
|
@@ -129,6 +134,10 @@ Switches between local disk and Cloudinary cloud storage.
|
|
|
129
134
|
Securely manages admin credentials.
|
|
130
135
|
* **Output:** `✅ Password for 'admin' updated successfully.`
|
|
131
136
|
|
|
137
|
+
#### `ofa maintenance [on|off]`
|
|
138
|
+
Instantly toggles a premium "Under Construction" page for visitors while allowing admin access.
|
|
139
|
+
* **Output:** `✅ Maintenance mode has been turned on.`
|
|
140
|
+
|
|
132
141
|
---
|
|
133
142
|
|
|
134
143
|
### 📁 Project Lifecycle
|
|
@@ -143,6 +152,7 @@ Securely manages admin credentials.
|
|
|
143
152
|
| `ofa swagger` | **OpenAPI Generation.** Auto-generates `openapi.json` for your entire application. |
|
|
144
153
|
| `ofa task NAME` | **Run Background Task.** Executes a task defined in `lib/tasks/`. |
|
|
145
154
|
| `ofa test` | **Run Test Suite.** Executes all unit tests in the `test/` directory using Minitest. |
|
|
155
|
+
| `ofa maintenance on/off` | **Toggle Site Access.** Activates a beautiful maintenance page. Admins can still bypass it to work. |
|
|
146
156
|
| `ofa deploy` | **Production Deployment.** Automatically detects deployment targets (Railway/Docker/Git). |
|
|
147
157
|
|
|
148
158
|
---
|
|
@@ -159,6 +169,7 @@ Automate the creation of boilerplate code with the generator command.
|
|
|
159
169
|
| `ofa g mailer NAME ACTION` | Generates a new mailer in `app/mailers/` and an ERB template in `app/views/mailers/`. |
|
|
160
170
|
| `ofa g task NAME` | Creates a new background task file in `lib/tasks/`. |
|
|
161
171
|
| `ofa g test NAME` | Generates a new Minitest unit test in `test/`. |
|
|
172
|
+
| `ofa g plugin NAME` | **Plugin Scaffolding.** Creates a new plugin structure in the `plugins/` directory. |
|
|
162
173
|
| `ofa g post TITLE` | Creates a new Markdown/ERB post in `app/views/posts/`. <br> *Args:* `--category`, `--author`, `--image`. <br> *Example:* `./ofa g post "My First Journey" --category Tech --author "John Doe"` |
|
|
163
174
|
|
|
164
175
|
---
|
|
@@ -46,6 +46,15 @@ class ApplicationController
|
|
|
46
46
|
@req.env['eks_cent.csrf_token']
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Activity Logger
|
|
50
|
+
def log_activity(action, target = nil, details = nil)
|
|
51
|
+
user_id = session['user_id'] || session[:user_id]
|
|
52
|
+
return unless user_id
|
|
53
|
+
ActivityLog.log(user_id, action, target, details)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
49
58
|
def boolean_param(val)
|
|
50
59
|
['1', 'true', 'on'].include?(val.to_s)
|
|
51
60
|
end
|
|
@@ -8,7 +8,8 @@ class DashboardController < ApplicationController
|
|
|
8
8
|
pages: Page.count,
|
|
9
9
|
posts: Post.count,
|
|
10
10
|
projects: Project.count,
|
|
11
|
-
products: defined?(Product) ? Product.count : 0
|
|
11
|
+
products: defined?(Product) ? Product.count : 0,
|
|
12
|
+
users: User.count
|
|
12
13
|
}
|
|
13
14
|
render 'dashboard'
|
|
14
15
|
end
|
|
@@ -17,6 +17,7 @@ class PagesController < ApplicationController
|
|
|
17
17
|
data['is_nav'] = boolean_param(data['is_nav'])
|
|
18
18
|
@page = Page.new(data)
|
|
19
19
|
if @page.save
|
|
20
|
+
log_activity("Created page: #{@page.title}", @page, "Slug: #{@page.slug}, Nav: #{@page.is_nav}")
|
|
20
21
|
redirect_to '/dashboard/pages'
|
|
21
22
|
else
|
|
22
23
|
render 'cms/pages_form'
|
|
@@ -34,6 +35,7 @@ class PagesController < ApplicationController
|
|
|
34
35
|
data['is_active'] = boolean_param(data['is_active'])
|
|
35
36
|
data['is_nav'] = boolean_param(data['is_nav'])
|
|
36
37
|
if @page.update(data)
|
|
38
|
+
log_activity("Updated page: #{@page.title}", @page, "New Data: #{data.reject{|k| k == 'content'}.to_json}")
|
|
37
39
|
redirect_to '/dashboard/pages'
|
|
38
40
|
else
|
|
39
41
|
render 'cms/pages_form'
|
|
@@ -42,8 +44,10 @@ class PagesController < ApplicationController
|
|
|
42
44
|
|
|
43
45
|
def destroy
|
|
44
46
|
@page = Page[params['id']]
|
|
47
|
+
title = @page.title
|
|
45
48
|
delete_all_images_from_content(@page.content) if @page.respond_to?(:content)
|
|
46
49
|
@page.destroy
|
|
50
|
+
log_activity("Deleted page: #{title}")
|
|
47
51
|
redirect_to '/dashboard/pages'
|
|
48
52
|
end
|
|
49
53
|
end
|
|
@@ -16,6 +16,7 @@ class PostsController < ApplicationController
|
|
|
16
16
|
data['is_active'] = boolean_param(data['is_active'])
|
|
17
17
|
@post = Post.new(data)
|
|
18
18
|
if @post.save
|
|
19
|
+
log_activity("Created post: #{@post.title}", @post, "Category: #{@post.category}")
|
|
19
20
|
redirect_to '/dashboard/posts'
|
|
20
21
|
else
|
|
21
22
|
render 'cms/posts_form'
|
|
@@ -32,6 +33,7 @@ class PostsController < ApplicationController
|
|
|
32
33
|
data = params['post']
|
|
33
34
|
data['is_active'] = boolean_param(data['is_active'])
|
|
34
35
|
if @post.update(data)
|
|
36
|
+
log_activity("Updated post: #{@post.title}", @post, "New Data: #{data.reject{|k| k == 'content'}.to_json}")
|
|
35
37
|
redirect_to '/dashboard/posts'
|
|
36
38
|
else
|
|
37
39
|
render 'cms/posts_form'
|
|
@@ -40,9 +42,11 @@ class PostsController < ApplicationController
|
|
|
40
42
|
|
|
41
43
|
def destroy
|
|
42
44
|
@post = Post[params['id']]
|
|
45
|
+
title = @post.title
|
|
43
46
|
delete_from_storage(@post.image_url) if @post.respond_to?(:image_url)
|
|
44
47
|
delete_all_images_from_content(@post.content) if @post.respond_to?(:content)
|
|
45
48
|
@post.destroy
|
|
49
|
+
log_activity("Deleted post: #{title}")
|
|
46
50
|
redirect_to '/dashboard/posts'
|
|
47
51
|
end
|
|
48
52
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class MaintenanceMiddleware
|
|
2
|
+
def initialize(app)
|
|
3
|
+
@app = app
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def call(env)
|
|
7
|
+
# Reload config to ensure instant mode change without server restart
|
|
8
|
+
config = JSON.parse(File.read(File.join(APP_ROOT, 'config', 'features.json'))) rescue (defined?(FEATURES_CONFIG) ? FEATURES_CONFIG : {})
|
|
9
|
+
|
|
10
|
+
if config['maintenance']
|
|
11
|
+
path = env['PATH_INFO']
|
|
12
|
+
|
|
13
|
+
# Allow access to login, static assets, and CMS dashboard (if admin)
|
|
14
|
+
# This allows admins to turn off maintenance mode via the dashboard
|
|
15
|
+
allowed_paths = ['/login', '/logout', '/images', '/css', '/js', '/favicon.ico']
|
|
16
|
+
is_allowed = allowed_paths.any? { |p| path.start_with?(p) }
|
|
17
|
+
|
|
18
|
+
# Also allow if already logged in (admin access)
|
|
19
|
+
session = env['eks_cent.session'] || env['rack.session'] || {}
|
|
20
|
+
is_admin = session['user_id'] || session[:user_id]
|
|
21
|
+
|
|
22
|
+
if !is_allowed && !is_admin
|
|
23
|
+
# Render maintenance page
|
|
24
|
+
res = EksCent::Response.new
|
|
25
|
+
res.render('maintenance', layout: false)
|
|
26
|
+
return [503, res.headers, res.body]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@app.call(env)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class ActivityLog < Sequel::Model
|
|
2
|
+
# Custom user lookup to support both SQL and Mongo users
|
|
3
|
+
def user
|
|
4
|
+
return @user if defined?(@user)
|
|
5
|
+
@user = User.find(id: self.user_id) rescue nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.log(user_id, action, target = nil, details = nil)
|
|
9
|
+
log_entry = {
|
|
10
|
+
user_id: user_id.to_s,
|
|
11
|
+
action: action,
|
|
12
|
+
created_at: Time.now
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if target
|
|
16
|
+
log_entry[:target_type] = target.class.name
|
|
17
|
+
log_entry[:target_id] = target.id.to_s rescue nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if details
|
|
21
|
+
log_entry[:details] = details.is_a?(Hash) ? details.to_json : details.to_s
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
create(log_entry)
|
|
25
|
+
end
|
|
26
|
+
end
|
data/app/models/user.rb
CHANGED
|
@@ -15,9 +15,23 @@ class User < Sequel::Model
|
|
|
15
15
|
defined?(MONGO_CLIENT) && MONGO_CLIENT
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Override count for Mongo support
|
|
19
|
+
def self.count
|
|
20
|
+
if mongo?
|
|
21
|
+
MONGO_CLIENT[:users].count_documents
|
|
22
|
+
else
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
# Override find for Mongo support
|
|
19
28
|
def self.find(params)
|
|
20
29
|
if mongo?
|
|
30
|
+
# Map :id to :_id and handle ObjectId conversion
|
|
31
|
+
if params[:id]
|
|
32
|
+
id_val = params.delete(:id)
|
|
33
|
+
params[:_id] = BSON::ObjectId.from_string(id_val) rescue id_val
|
|
34
|
+
end
|
|
21
35
|
doc = MONGO_CLIENT[:users].find(params).first
|
|
22
36
|
return nil unless doc
|
|
23
37
|
user = User.new
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<div class="glass-card">
|
|
2
|
+
<div class="card-header flex justify-between items-center">
|
|
3
|
+
<div>
|
|
4
|
+
<h2 class="card-title">Activity Logs (Audit Trail)</h2>
|
|
5
|
+
<p class="card-subtitle">Keep track of who did what in the CMS dashboard.</p>
|
|
6
|
+
</div>
|
|
7
|
+
<a href="/dashboard" class="btn-premium !py-2 !px-4 text-sm flex items-center gap-2">
|
|
8
|
+
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
|
9
|
+
</a>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="table-container">
|
|
13
|
+
<table class="premium-table">
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
<th>Time</th>
|
|
17
|
+
<th>User</th>
|
|
18
|
+
<th>Action</th>
|
|
19
|
+
<th>Target</th>
|
|
20
|
+
<th>Details</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody>
|
|
24
|
+
<% if @logs.any? %>
|
|
25
|
+
<% @logs.each do |log| %>
|
|
26
|
+
<tr>
|
|
27
|
+
<td data-label="Time" class="text-sm opacity-70">
|
|
28
|
+
<%= log.created_at.strftime('%Y-%m-%d %H:%M:%S') %>
|
|
29
|
+
</td>
|
|
30
|
+
<td data-label="User">
|
|
31
|
+
<div class="flex items-center gap-2">
|
|
32
|
+
<div class="avatar-mini">
|
|
33
|
+
<%= log.user ? log.user.username[0].upcase : '?' %>
|
|
34
|
+
</div>
|
|
35
|
+
<span><%= log.user ? log.user.username : 'Unknown' %></span>
|
|
36
|
+
</div>
|
|
37
|
+
</td>
|
|
38
|
+
<td data-label="Action">
|
|
39
|
+
<span class="badge badge-primary">
|
|
40
|
+
<%= log.action %>
|
|
41
|
+
</span>
|
|
42
|
+
</td>
|
|
43
|
+
<td data-label="Target">
|
|
44
|
+
<% if log.target_type %>
|
|
45
|
+
<span class="text-xs opacity-60"><%= log.target_type %></span>
|
|
46
|
+
<code class="text-xs">#<%= log.target_id %></code>
|
|
47
|
+
<% else %>
|
|
48
|
+
-
|
|
49
|
+
<% end %>
|
|
50
|
+
</td>
|
|
51
|
+
<td data-label="Details" class="text-xs opacity-80 max-w-xs truncate">
|
|
52
|
+
<%= log.details || '-' %>
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% else %>
|
|
57
|
+
<tr>
|
|
58
|
+
<td colspan="5" class="text-center py-8 opacity-50">
|
|
59
|
+
No logs found yet.
|
|
60
|
+
</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<% end %>
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
.avatar-mini {
|
|
70
|
+
width: 24px;
|
|
71
|
+
height: 24px;
|
|
72
|
+
background: var(--primary);
|
|
73
|
+
color: white;
|
|
74
|
+
border-radius: 50%;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
font-size: 0.7rem;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.premium-table {
|
|
83
|
+
width: 100%;
|
|
84
|
+
border-collapse: collapse;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.premium-table th {
|
|
88
|
+
text-align: left;
|
|
89
|
+
padding: 1rem;
|
|
90
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
91
|
+
font-size: 0.85rem;
|
|
92
|
+
text-transform: uppercase;
|
|
93
|
+
letter-spacing: 0.05em;
|
|
94
|
+
opacity: 0.7;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.premium-table td {
|
|
98
|
+
padding: 1rem;
|
|
99
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
100
|
+
font-size: 0.9rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.premium-table tr:hover {
|
|
104
|
+
background: rgba(255, 255, 255, 0.02);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.badge {
|
|
108
|
+
padding: 0.2rem 0.6rem;
|
|
109
|
+
border-radius: 99px;
|
|
110
|
+
font-size: 0.75rem;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.badge-primary {
|
|
115
|
+
background: rgba(99, 102, 241, 0.2);
|
|
116
|
+
color: #818cf8;
|
|
117
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.table-container {
|
|
121
|
+
overflow-x: auto;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Responsive adjustments */
|
|
125
|
+
@media (max-width: 768px) {
|
|
126
|
+
.card-header {
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
align-items: flex-start !important;
|
|
129
|
+
gap: 1rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.premium-table thead {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.premium-table tr {
|
|
137
|
+
display: block;
|
|
138
|
+
margin-bottom: 1.5rem;
|
|
139
|
+
background: rgba(255, 255, 255, 0.03);
|
|
140
|
+
border-radius: 12px;
|
|
141
|
+
padding: 0.5rem;
|
|
142
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.premium-table td {
|
|
146
|
+
display: flex;
|
|
147
|
+
justify-content: space-between;
|
|
148
|
+
align-items: center;
|
|
149
|
+
padding: 0.75rem 1rem;
|
|
150
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
|
151
|
+
text-align: right;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.premium-table td:last-child {
|
|
155
|
+
border-bottom: none;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.premium-table td::before {
|
|
159
|
+
content: attr(data-label);
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
font-size: 0.75rem;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
opacity: 0.5;
|
|
164
|
+
text-align: left;
|
|
165
|
+
margin-right: 1rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.premium-table td .flex {
|
|
169
|
+
justify-content: flex-end;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.truncate {
|
|
173
|
+
white-space: normal;
|
|
174
|
+
max-width: 60%;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</style>
|
data/app/views/dashboard.erb
CHANGED
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
<span class="font-semibold">Products</span>
|
|
36
36
|
</a>
|
|
37
37
|
<% end %>
|
|
38
|
+
|
|
39
|
+
<a href="/dashboard/logs" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 <%= @req.path.start_with?('/dashboard/logs') ? 'bg-primary text-white shadow-lg shadow-primary/30' : 'text-slate-500 hover:bg-white/5' %>">
|
|
40
|
+
<i class="fas fa-history w-5"></i>
|
|
41
|
+
<span class="font-semibold">Activity Logs</span>
|
|
42
|
+
</a>
|
|
38
43
|
|
|
39
44
|
<div class="h-px bg-white/5 my-4"></div>
|
|
40
45
|
|
|
@@ -111,6 +116,97 @@
|
|
|
111
116
|
<div class="text-lg font-black text-slate-700 dark:text-white uppercase tracking-wider truncate"><%= FEATURES_CONFIG['storage'] || 'Local Cluster' %></div>
|
|
112
117
|
</div>
|
|
113
118
|
</div>
|
|
119
|
+
|
|
120
|
+
<!-- System Analytics Chart -->
|
|
121
|
+
<div class="glass-card !p-8 group">
|
|
122
|
+
<div class="card-glow !bg-primary/5"></div>
|
|
123
|
+
<div class="flex items-center justify-between mb-8">
|
|
124
|
+
<div>
|
|
125
|
+
<h3 class="text-2xl font-black tracking-tighter text-slate-700 dark:text-white">System Analytics</h3>
|
|
126
|
+
<p class="text-slate-500 text-sm">Real-time content distribution and user growth</p>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="flex gap-2">
|
|
129
|
+
<span class="w-3 h-3 rounded-full bg-primary animate-pulse"></span>
|
|
130
|
+
<span class="text-[10px] font-bold text-primary uppercase tracking-widest">Live Data</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
|
135
|
+
<div class="relative h-[300px] w-full">
|
|
136
|
+
<canvas id="statsChart"></canvas>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="space-y-6">
|
|
139
|
+
<div class="p-6 rounded-3xl bg-white/5 border border-white/10 hover:border-primary/30 transition-all group/stat">
|
|
140
|
+
<div class="flex items-center justify-between mb-2">
|
|
141
|
+
<span class="text-slate-500 font-bold text-xs uppercase tracking-widest">Active Users</span>
|
|
142
|
+
<i class="fas fa-users text-primary"></i>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="text-3xl font-black text-slate-700 dark:text-white tracking-tighter"><%= @stats[:users] %></div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="p-6 rounded-3xl bg-white/5 border border-white/10 hover:border-primary/30 transition-all group/stat">
|
|
147
|
+
<div class="flex items-center justify-between mb-2">
|
|
148
|
+
<span class="text-slate-500 font-bold text-xs uppercase tracking-widest">Total Assets</span>
|
|
149
|
+
<i class="fas fa-database text-secondary"></i>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="text-3xl font-black text-slate-700 dark:text-white tracking-tighter"><%= @stats[:pages] + @stats[:posts] + @stats[:projects] + @stats[:products] %></div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
159
|
+
const ctx = document.getElementById('statsChart').getContext('2d');
|
|
160
|
+
|
|
161
|
+
const stats = {
|
|
162
|
+
pages: <%= @stats[:pages] %>,
|
|
163
|
+
posts: <%= @stats[:posts] %>,
|
|
164
|
+
projects: <%= @stats[:projects] %>,
|
|
165
|
+
products: <%= @stats[:products] %>,
|
|
166
|
+
users: <%= @stats[:users] %>
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
new Chart(ctx, {
|
|
170
|
+
type: 'doughnut',
|
|
171
|
+
data: {
|
|
172
|
+
labels: ['Pages', 'Posts', 'Projects', 'Products', 'Users'],
|
|
173
|
+
datasets: [{
|
|
174
|
+
data: [stats.pages, stats.posts, stats.projects, stats.products, stats.users],
|
|
175
|
+
backgroundColor: [
|
|
176
|
+
'#4f46e5', // Primary (Indigo)
|
|
177
|
+
'#8b5cf6', // Secondary (Purple)
|
|
178
|
+
'#10b981', // Green
|
|
179
|
+
'#f59e0b', // Orange
|
|
180
|
+
'#ec4899' // Pink
|
|
181
|
+
],
|
|
182
|
+
borderWidth: 0,
|
|
183
|
+
hoverOffset: 20
|
|
184
|
+
}]
|
|
185
|
+
},
|
|
186
|
+
options: {
|
|
187
|
+
responsive: true,
|
|
188
|
+
maintainAspectRatio: false,
|
|
189
|
+
cutout: '75%',
|
|
190
|
+
plugins: {
|
|
191
|
+
legend: {
|
|
192
|
+
display: true,
|
|
193
|
+
position: 'bottom',
|
|
194
|
+
labels: {
|
|
195
|
+
color: document.documentElement.classList.contains('dark') ? '#94a3b8' : '#64748b',
|
|
196
|
+
font: {
|
|
197
|
+
family: 'Outfit',
|
|
198
|
+
weight: '600',
|
|
199
|
+
size: 12
|
|
200
|
+
},
|
|
201
|
+
padding: 20,
|
|
202
|
+
usePointStyle: true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
</script>
|
|
114
210
|
|
|
115
211
|
<div class="glass-card !p-12 bg-gradient-to-br from-primary/5 to-transparent border-primary/10 text-left">
|
|
116
212
|
<div class="max-w-xl">
|
data/app/views/docs.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="space-y-12 pb-20">
|
|
2
2
|
<div class="text-left">
|
|
3
|
-
<div class="badge-premium">Documentation
|
|
3
|
+
<div class="badge-premium">Documentation v5.0.0</div>
|
|
4
4
|
<h1 class="text-5xl font-black tracking-tighter mb-4 text-slate-700 dark:text-white">Framework <span class="text-primary">Guide</span></h1>
|
|
5
5
|
<p class="text-xl text-slate-500 max-w-2xl leading-relaxed">Everything you need to know about building premium web applications with the One-For-All framework.</p>
|
|
6
6
|
</div>
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
<a href="#structure" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Project Structure</a>
|
|
16
16
|
<a href="#routing" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Routing System</a>
|
|
17
17
|
<a href="#cms" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">CMS & Features</a>
|
|
18
|
+
<a href="#uiux" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">UI/UX & Analytics</a>
|
|
18
19
|
<a href="#ecommerce" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">E-Commerce</a>
|
|
19
20
|
<a href="#api" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">API & Modern Web</a>
|
|
20
21
|
<a href="#advanced" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Mailers & Background Jobs</a>
|
|
21
22
|
<a href="#testing" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Automated Testing</a>
|
|
23
|
+
<a href="#plugins" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Plugin System</a>
|
|
22
24
|
<a href="#deploy" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Deployment</a>
|
|
23
25
|
</nav>
|
|
24
26
|
</aside>
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
<i class="fas fa-star text-primary"></i> Introduction
|
|
32
34
|
</h2>
|
|
33
35
|
<div class="markdown-content">
|
|
34
|
-
<p><strong>One-For-All (OFA)</strong> is a
|
|
36
|
+
<p><strong>One-For-All (OFA)</strong> is a modern and powerful web application framework designed for developers who value high performance, scalability, and premium aesthetics. Built on the high-performance <strong>Eksa Server</strong> engine, OFA v5.0.0 introduces advanced dashboard analytics, native Lucide icon support, and a robust plugin architecture.</p>
|
|
35
37
|
<p>Our philosophy is "Premium by Default". You shouldn't have to spend hours tweaking CSS just to get a modern look. With OFA, you get Glassmorphism, animations, and responsive layouts baked in.</p>
|
|
36
38
|
</div>
|
|
37
39
|
</section>
|
|
@@ -115,6 +117,10 @@
|
|
|
115
117
|
<td class="px-6 py-4 font-mono text-primary">./ofa g test NAME</td>
|
|
116
118
|
<td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates a Minitest unit test file.</td>
|
|
117
119
|
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td class="px-6 py-4 font-mono text-primary">./ofa g plugin NAME</td>
|
|
122
|
+
<td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Scaffolds a new plugin structure in <code>/plugins</code>.</td>
|
|
123
|
+
</tr>
|
|
118
124
|
|
|
119
125
|
<tr class="bg-slate-500/5"><td colspan="2" class="px-6 py-2 text-[10px] font-black uppercase tracking-tighter text-slate-400">Customization</td></tr>
|
|
120
126
|
<tr>
|
|
@@ -303,6 +309,56 @@ resources :posts
|
|
|
303
309
|
<li>• <strong>Image Handling:</strong> Drag-and-drop images directly into the editor with auto-storage sync.</li>
|
|
304
310
|
<li>• <strong>Code Blocks:</strong> Native support for code blocks with one-click "Copy" functionality.</li>
|
|
305
311
|
</ul>
|
|
312
|
+
|
|
313
|
+
<h4 class="font-bold text-lg mt-8 mb-4">🔍 Activity Logs (Audit Trail)</h4>
|
|
314
|
+
<p class="text-sm text-slate-500 mb-4">Track every action taken by users in the CMS. Automatically logs creation, updates, and deletions of pages and posts.</p>
|
|
315
|
+
<ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
|
|
316
|
+
<li>• <strong>User Identification:</strong> Logs include the username and avatar of the person who performed the action.</li>
|
|
317
|
+
<li>• <strong>Detailed Context:</strong> Records specific changes, such as new slugs or modified categories.</li>
|
|
318
|
+
<li>• <strong>Database Agnostic:</strong> Works seamlessly with both SQL and MongoDB storage.</li>
|
|
319
|
+
</ul>
|
|
320
|
+
|
|
321
|
+
<h4 class="font-bold text-lg mt-8 mb-4">🚧 Maintenance Mode</h4>
|
|
322
|
+
<p class="text-sm text-slate-500 mb-4">Instantly activate a professional "Under Construction" page for your visitors using the CLI. Perfect for updates or migrations.</p>
|
|
323
|
+
<div class="p-4 bg-white/5 border border-white/10 rounded-2xl mb-4 font-mono text-sm">
|
|
324
|
+
./ofa maintenance on<br>
|
|
325
|
+
./ofa maintenance off
|
|
326
|
+
</div>
|
|
327
|
+
<ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
|
|
328
|
+
<li>• <strong>Admin Bypass:</strong> Logged-in administrators can still view and manage the site while maintenance is active.</li>
|
|
329
|
+
<li>• <strong>Premium Design:</strong> Features a high-quality Glassmorphism roket animation to keep your brand looking professional even when offline.</li>
|
|
330
|
+
<li>• <strong>Dynamic Updates:</strong> Changes take effect instantly without requiring a server restart.</li>
|
|
331
|
+
</ul>
|
|
332
|
+
</div>
|
|
333
|
+
</section>
|
|
334
|
+
|
|
335
|
+
<!-- UI/UX & Analytics -->
|
|
336
|
+
<section id="uiux" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-secondary/10 to-transparent">
|
|
337
|
+
<h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
|
|
338
|
+
<i class="fas fa-wand-magic-sparkles text-primary"></i> UI/UX & Analytics
|
|
339
|
+
</h2>
|
|
340
|
+
<div class="markdown-content">
|
|
341
|
+
<p>Version 5.0.0 brings significant enhancements to the visual and analytical capabilities of the framework.</p>
|
|
342
|
+
|
|
343
|
+
<h4 class="font-bold text-lg mt-8 mb-4">📊 Dashboard Analytics (Chart.js)</h4>
|
|
344
|
+
<p class="text-sm text-slate-500 mb-4">The CMS dashboard now includes real-time visualizations of your application's data. Powered by Chart.js, the analytics widget provides a clear overview of your content distribution and user growth.</p>
|
|
345
|
+
<ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
|
|
346
|
+
<li>• <strong>Doughnut Chart:</strong> Visualizes the ratio of pages, posts, projects, products, and users.</li>
|
|
347
|
+
<li>• <strong>Live Stats:</strong> Real-time counters for "Active Users" and "Total Assets".</li>
|
|
348
|
+
<li>• <strong>Theme-Aware:</strong> The chart's colors and legend automatically adjust to match your chosen theme (Light/Dark).</li>
|
|
349
|
+
</ul>
|
|
350
|
+
|
|
351
|
+
<h4 class="font-bold text-lg mt-8 mb-4">🎨 Lucide Icons Integration</h4>
|
|
352
|
+
<p class="text-sm text-slate-500 mb-4">In addition to FontAwesome, OFA now natively supports **Lucide Icons**. Lucide provides a modern, lightweight, and consistent set of SVG icons that fit perfectly with the framework's minimalist aesthetic.</p>
|
|
353
|
+
<div class="p-4 bg-white/5 border border-white/10 rounded-2xl mb-4">
|
|
354
|
+
<p class="text-xs text-slate-500 mb-2">Usage Example:</p>
|
|
355
|
+
<code class="text-primary"><i data-lucide="rocket"></i></code>
|
|
356
|
+
</div>
|
|
357
|
+
<ul class="text-xs space-y-2 text-slate-600 dark:text-slate-400">
|
|
358
|
+
<li>• <strong>Automatic Replacement:</strong> Icons are automatically initialized and rendered on page load.</li>
|
|
359
|
+
<li>• <strong>SVG Based:</strong> Scalable and CSS-stylable icons without the overhead of webfonts.</li>
|
|
360
|
+
<li>• <strong>Vast Library:</strong> Access to hundreds of modern icons for any use case.</li>
|
|
361
|
+
</ul>
|
|
306
362
|
</div>
|
|
307
363
|
</section>
|
|
308
364
|
|
|
@@ -405,6 +461,33 @@ resources :posts
|
|
|
405
461
|
</div>
|
|
406
462
|
</section>
|
|
407
463
|
|
|
464
|
+
<!-- Plugin System -->
|
|
465
|
+
<section id="plugins" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
|
|
466
|
+
<h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
|
|
467
|
+
<i class="fas fa-plug text-primary"></i> Plugin System
|
|
468
|
+
</h2>
|
|
469
|
+
<div class="markdown-content">
|
|
470
|
+
<p>OFA v4.5.0 introduces a powerful <strong>Plugin System</strong> that allows you to extend the framework without modifying the core files. Plugins are located in the <code>/plugins</code> directory.</p>
|
|
471
|
+
|
|
472
|
+
<h4 class="font-bold text-lg mt-8 mb-4">🛠️ Working with Plugins</h4>
|
|
473
|
+
<div class="p-4 bg-white/5 border border-white/10 rounded-2xl mb-4 font-mono text-sm">
|
|
474
|
+
./ofa g plugin my_awesome_feature
|
|
475
|
+
</div>
|
|
476
|
+
<p class="text-sm text-slate-500 mb-4">Each plugin has an <code>init.rb</code> file where you can register hooks and routes:</p>
|
|
477
|
+
<div class="p-4 bg-slate-900 rounded-2xl font-mono text-xs text-green-400 mb-6">
|
|
478
|
+
<pre>
|
|
479
|
+
OFA.on_boot do
|
|
480
|
+
puts "Plugin Loaded!"
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
OFA.add_route(:get, "/my-plugin") do |req, res|
|
|
484
|
+
res.body << "Hello from Plugin!"
|
|
485
|
+
end
|
|
486
|
+
</pre>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
</section>
|
|
490
|
+
|
|
408
491
|
<!-- Deployment -->
|
|
409
492
|
<section id="deploy" class="glass-panel p-8 md:p-12 text-left border-primary/20">
|
|
410
493
|
<h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
|
data/app/views/index.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="text-center space-y-6 max-w-2xl mx-auto py-12">
|
|
2
2
|
<div class="inline-flex items-center px-4 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider animate-pulse">
|
|
3
|
-
Framework Version
|
|
3
|
+
Framework Version 5.0.0
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
6
|
<h1 class="text-5xl md:text-7xl font-black tracking-tight leading-tight">
|
data/app/views/layout.erb
CHANGED
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
22
22
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-ruby.min.js"></script>
|
|
23
23
|
|
|
24
|
+
<!-- Lucide Icons -->
|
|
25
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
26
|
+
|
|
27
|
+
<!-- Chart.js -->
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
29
|
+
|
|
24
30
|
<% if FEATURES_CONFIG['rich_text'] %>
|
|
25
31
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.8/dist/trix.css">
|
|
26
32
|
<script type="text/javascript" src="https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js"></script>
|
|
@@ -584,6 +590,9 @@
|
|
|
584
590
|
|
|
585
591
|
<script>
|
|
586
592
|
document.addEventListener('DOMContentLoaded', () => {
|
|
593
|
+
// Initialize Lucide Icons
|
|
594
|
+
lucide.createIcons();
|
|
595
|
+
|
|
587
596
|
// Initial Page Load Animation
|
|
588
597
|
anime({
|
|
589
598
|
targets: 'header nav',
|
|
@@ -0,0 +1,143 @@
|
|
|
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>Under Construction | One-For-All</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--primary: #6366f1;
|
|
11
|
+
--primary-glow: rgba(99, 102, 241, 0.5);
|
|
12
|
+
--bg: #0f172a;
|
|
13
|
+
--glass: rgba(255, 255, 255, 0.05);
|
|
14
|
+
--glass-border: rgba(255, 255, 255, 0.1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
font-family: 'Outfit', sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: white;
|
|
27
|
+
height: 100vh;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
background-image:
|
|
33
|
+
radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
|
|
34
|
+
radial-gradient(circle at 80% 80%, rgba(236, 72, 153, 0.15) 0%, transparent 40%);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.container {
|
|
38
|
+
text-align: center;
|
|
39
|
+
padding: 3rem;
|
|
40
|
+
background: var(--glass);
|
|
41
|
+
backdrop-filter: blur(20px);
|
|
42
|
+
border: 1px solid var(--glass-border);
|
|
43
|
+
border-radius: 2rem;
|
|
44
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
45
|
+
max-width: 600px;
|
|
46
|
+
width: 90%;
|
|
47
|
+
animation: fadeIn 1s ease-out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes fadeIn {
|
|
51
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
52
|
+
to { opacity: 1; transform: translateY(0); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.icon {
|
|
56
|
+
font-size: 5rem;
|
|
57
|
+
margin-bottom: 1.5rem;
|
|
58
|
+
display: inline-block;
|
|
59
|
+
animation: float 3s ease-in-out infinite;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes float {
|
|
63
|
+
0%, 100% { transform: translateY(0); }
|
|
64
|
+
50% { transform: translateY(-10px); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
h1 {
|
|
68
|
+
font-size: 2.5rem;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
margin-bottom: 1rem;
|
|
71
|
+
background: linear-gradient(to right, #818cf8, #f472b6);
|
|
72
|
+
-webkit-background-clip: text;
|
|
73
|
+
-webkit-text-fill-color: transparent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
p {
|
|
77
|
+
font-size: 1.1rem;
|
|
78
|
+
color: #94a3b8;
|
|
79
|
+
line-height: 1.6;
|
|
80
|
+
margin-bottom: 2rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.loader {
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 4px;
|
|
86
|
+
background: rgba(255, 255, 255, 0.1);
|
|
87
|
+
border-radius: 2px;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
margin-bottom: 2rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.loader-bar {
|
|
93
|
+
width: 40%;
|
|
94
|
+
height: 100%;
|
|
95
|
+
background: var(--primary);
|
|
96
|
+
box-shadow: 0 0 15px var(--primary-glow);
|
|
97
|
+
border-radius: 2px;
|
|
98
|
+
animation: loading 2s infinite ease-in-out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes loading {
|
|
102
|
+
0% { transform: translateX(-100%); }
|
|
103
|
+
100% { transform: translateX(300%); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.footer {
|
|
107
|
+
font-size: 0.9rem;
|
|
108
|
+
color: #64748b;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.admin-link {
|
|
112
|
+
margin-top: 2rem;
|
|
113
|
+
display: block;
|
|
114
|
+
color: var(--primary);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
font-weight: 500;
|
|
117
|
+
opacity: 0.5;
|
|
118
|
+
transition: opacity 0.3s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.admin-link:hover {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<div class="container">
|
|
128
|
+
<div class="icon">🚀</div>
|
|
129
|
+
<h1>Under Construction</h1>
|
|
130
|
+
<p>We're working hard to bring you something amazing. Our site is currently undergoing scheduled maintenance. Please check back soon!</p>
|
|
131
|
+
|
|
132
|
+
<div class="loader">
|
|
133
|
+
<div class="loader-bar"></div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="footer">
|
|
137
|
+
© <%= Time.now.year %> One-For-All Framework. All rights reserved.
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<a href="/login" class="admin-link">Admin Login</a>
|
|
141
|
+
</div>
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
data/bin/ofa
CHANGED
|
@@ -33,7 +33,7 @@ def help
|
|
|
33
33
|
puts " / __ \\/ ____/ / | "
|
|
34
34
|
puts " / / / / /_ / /| | Framework "
|
|
35
35
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
36
|
-
puts "\\____/_/ /_/ |_|
|
|
36
|
+
puts "\\____/_/ /_/ |_| v5.0.0 "
|
|
37
37
|
puts " "
|
|
38
38
|
puts "✨ One-For-All Framework CLI ✨"
|
|
39
39
|
puts "-----------------------------"
|
|
@@ -61,6 +61,7 @@ def help
|
|
|
61
61
|
puts " ofa doctor - Check system health (DB, config, gems)"
|
|
62
62
|
puts " ofa routes - List all registered routes"
|
|
63
63
|
puts " ofa swagger - Auto-generate OpenAPI/Swagger documentation"
|
|
64
|
+
puts " ofa maintenance ACTION - Toggle maintenance mode (on/off)"
|
|
64
65
|
puts " ofa task NAME - Run a background task"
|
|
65
66
|
puts " ofa test - Run all application tests"
|
|
66
67
|
puts " ofa run - Start the application server"
|
|
@@ -247,7 +248,7 @@ when 'init'
|
|
|
247
248
|
puts " / __ \\/ ____/ / | "
|
|
248
249
|
puts " / / / / /_ / /| | Framework "
|
|
249
250
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
250
|
-
puts "\\____/_/ /_/ |_|
|
|
251
|
+
puts "\\____/_/ /_/ |_| v5.0.0 "
|
|
251
252
|
puts " "
|
|
252
253
|
puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
|
|
253
254
|
|
|
@@ -327,12 +328,16 @@ when 'init'
|
|
|
327
328
|
# Middleware setup
|
|
328
329
|
use EksCent::Middleware::ShowExceptions
|
|
329
330
|
use EksCent::Middleware::Session, secret: EksCent.secret_key_base
|
|
331
|
+
use MaintenanceMiddleware
|
|
330
332
|
use AuthMiddleware
|
|
331
333
|
use JwtMiddleware
|
|
332
334
|
use CSRFMiddleware
|
|
333
335
|
use EksCent::Middleware::Static, root: 'public'
|
|
334
336
|
use EksCent::Middleware::Logger
|
|
335
337
|
|
|
338
|
+
# Apply plugin routes
|
|
339
|
+
OFA.apply_pending_routes(ROUTES) if defined?(OFA)
|
|
340
|
+
|
|
336
341
|
run ROUTES
|
|
337
342
|
RUBY
|
|
338
343
|
File.write(File.join(PROJECT_ROOT, 'config.ru'), config_ru_content)
|
|
@@ -619,8 +624,28 @@ when 'g'
|
|
|
619
624
|
content = "<h1>#{name}</h1>\n<p>Author: #{options[:author] || 'Admin'}</p>\n<p>Category: #{options[:category] || 'General'}</p>"
|
|
620
625
|
File.write(file_name, content)
|
|
621
626
|
puts "Created post: #{file_name}"
|
|
627
|
+
elsif sub == 'plugin' && name
|
|
628
|
+
dir_path = File.join(PROJECT_ROOT, "plugins/#{name.downcase}")
|
|
629
|
+
FileUtils.mkdir_p(dir_path)
|
|
630
|
+
file_name = File.join(dir_path, "init.rb")
|
|
631
|
+
content = <<~RUBY
|
|
632
|
+
# Plugin: #{name.capitalize}
|
|
633
|
+
# Created at: #{Time.now}
|
|
634
|
+
|
|
635
|
+
OFA.on_boot do
|
|
636
|
+
puts "🚀 Plugin #{name.capitalize} loaded!"
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Example route added by plugin
|
|
640
|
+
# OFA.add_route(:get, '/#{name.downcase}') do |req, res|
|
|
641
|
+
# res.body << "Hello from #{name.capitalize} Plugin!"
|
|
642
|
+
# end
|
|
643
|
+
RUBY
|
|
644
|
+
File.write(file_name, content)
|
|
645
|
+
puts "Created Plugin: #{dir_path}"
|
|
646
|
+
puts "💡 Edit #{file_name} to start building your plugin."
|
|
622
647
|
else
|
|
623
|
-
puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post] NAME"
|
|
648
|
+
puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post|plugin] NAME"
|
|
624
649
|
end
|
|
625
650
|
|
|
626
651
|
when 'feature'
|
|
@@ -681,6 +706,19 @@ when 'storage'
|
|
|
681
706
|
File.write(config_path, JSON.pretty_generate(config))
|
|
682
707
|
puts "Application storage set to '#{name}'."
|
|
683
708
|
|
|
709
|
+
when 'maintenance'
|
|
710
|
+
ensure_initialized!
|
|
711
|
+
action = ARGV.shift
|
|
712
|
+
unless %w[on off].include?(action)
|
|
713
|
+
puts "Usage: ofa maintenance [on|off]"
|
|
714
|
+
exit 1
|
|
715
|
+
end
|
|
716
|
+
config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
|
|
717
|
+
config = JSON.parse(File.read(config_path))
|
|
718
|
+
config['maintenance'] = (action == 'on')
|
|
719
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
720
|
+
puts "Maintenance mode has been turned #{action}."
|
|
721
|
+
|
|
684
722
|
when 'reset-password'
|
|
685
723
|
ENV['SKIP_MODELS'] = '1'
|
|
686
724
|
Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
|
|
@@ -823,7 +861,7 @@ when 'console'
|
|
|
823
861
|
puts " / __ \\/ ____/ / | "
|
|
824
862
|
puts " / / / / /_ / /| | Framework "
|
|
825
863
|
puts "/ /_/ / __/ / ___ | Console (REPL) "
|
|
826
|
-
puts "\\____/_/ /_/ |_|
|
|
864
|
+
puts "\\____/_/ /_/ |_| v5.0.0 "
|
|
827
865
|
puts " "
|
|
828
866
|
puts "✨ Loading environment... (Type 'exit' to quit)"
|
|
829
867
|
|
data/config/boot.rb
CHANGED
|
@@ -9,6 +9,8 @@ begin
|
|
|
9
9
|
rescue LoadError
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
require_relative '../lib/plugin_helper'
|
|
13
|
+
|
|
12
14
|
# Basic project structure constants
|
|
13
15
|
APP_ROOT ||= File.expand_path('..', __dir__)
|
|
14
16
|
|
|
@@ -110,6 +112,14 @@ framework_app = File.expand_path('../app', __dir__)
|
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
|
|
115
|
+
# Load Plugins
|
|
116
|
+
plugins_dir = File.join(APP_ROOT, 'plugins')
|
|
117
|
+
if File.directory?(plugins_dir)
|
|
118
|
+
Dir.glob(File.join(plugins_dir, '**', '*.rb')).each do |plugin_file|
|
|
119
|
+
require plugin_file
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
113
123
|
# ─── Routes DSL Extensions ─────────────────────────────────────────────────────
|
|
114
124
|
# Extends EksCent::Router with a `resources` helper that auto-generates RESTful
|
|
115
125
|
# routes (index, show, new, create, edit, update, destroy) for a given resource.
|
|
@@ -151,3 +161,6 @@ end
|
|
|
151
161
|
|
|
152
162
|
# Load routes
|
|
153
163
|
require_relative 'routes'
|
|
164
|
+
|
|
165
|
+
# Run Plugin Boot Hooks
|
|
166
|
+
OFA.run_boot_hooks if defined?(OFA)
|
data/config/database.rb
CHANGED
|
@@ -130,6 +130,18 @@ if DB
|
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
unless DB.table_exists?(:activity_logs)
|
|
134
|
+
DB.create_table :activity_logs do
|
|
135
|
+
primary_key :id
|
|
136
|
+
String :user_id
|
|
137
|
+
String :action, null: false
|
|
138
|
+
String :target_type
|
|
139
|
+
String :target_id
|
|
140
|
+
Text :details
|
|
141
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
133
145
|
# Quick Migration for existing tables
|
|
134
146
|
if DB.table_exists?(:pages)
|
|
135
147
|
DB.alter_table(:pages) { add_column :is_active, TrueClass, default: true unless DB[:pages].columns.include?(:is_active) }
|
data/config/features.json
CHANGED
data/config/routes.rb
CHANGED
|
@@ -61,6 +61,10 @@ ROUTES = EksCent::Router.new do
|
|
|
61
61
|
DashboardController.new(req, res).upload
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
get '/dashboard/logs' do |req, res|
|
|
65
|
+
ActivityLogsController.new(req, res).index
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
# Resourceful CMS Routes
|
|
65
69
|
resources :pages, prefix: '/dashboard'
|
|
66
70
|
resources :posts, prefix: '/dashboard'
|
data/config.ru
CHANGED
|
@@ -3,9 +3,13 @@ require_relative 'config/boot'
|
|
|
3
3
|
# Middleware setup
|
|
4
4
|
use EksCent::Middleware::ShowExceptions
|
|
5
5
|
use EksCent::Middleware::Session, secret: EksCent.secret_key_base
|
|
6
|
+
use MaintenanceMiddleware
|
|
6
7
|
use AuthMiddleware
|
|
7
8
|
use CSRFMiddleware
|
|
8
9
|
use EksCent::Middleware::Static, root: 'public'
|
|
9
10
|
use EksCent::Middleware::Logger
|
|
10
11
|
|
|
12
|
+
# Apply plugin routes
|
|
13
|
+
OFA.apply_pending_routes(ROUTES) if defined?(OFA)
|
|
14
|
+
|
|
11
15
|
run ROUTES
|
data/db/data.sqlite3
CHANGED
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
change do
|
|
3
|
+
create_table(:activity_logs) do
|
|
4
|
+
primary_key :id
|
|
5
|
+
String :user_id
|
|
6
|
+
String :action, null: false
|
|
7
|
+
String :target_type
|
|
8
|
+
String :target_id
|
|
9
|
+
Text :details
|
|
10
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: one-for-all-framework
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ishikawa Uta
|
|
@@ -200,6 +200,7 @@ files:
|
|
|
200
200
|
- LICENSE
|
|
201
201
|
- Procfile
|
|
202
202
|
- README.md
|
|
203
|
+
- app/controllers/activity_logs_controller.rb
|
|
203
204
|
- app/controllers/api_controller.rb
|
|
204
205
|
- app/controllers/application_controller.rb
|
|
205
206
|
- app/controllers/auth_controller.rb
|
|
@@ -214,6 +215,8 @@ files:
|
|
|
214
215
|
- app/middleware/auth_middleware.rb
|
|
215
216
|
- app/middleware/csrf_middleware.rb
|
|
216
217
|
- app/middleware/jwt_middleware.rb
|
|
218
|
+
- app/middleware/maintenance_middleware.rb
|
|
219
|
+
- app/models/activity_log.rb
|
|
217
220
|
- app/models/page.rb
|
|
218
221
|
- app/models/post.rb
|
|
219
222
|
- app/models/product.rb
|
|
@@ -221,6 +224,7 @@ files:
|
|
|
221
224
|
- app/models/user.rb
|
|
222
225
|
- app/views/blog_home.erb
|
|
223
226
|
- app/views/cart_index.erb
|
|
227
|
+
- app/views/cms/activity_logs.erb
|
|
224
228
|
- app/views/cms/pages_form.erb
|
|
225
229
|
- app/views/cms/pages_index.erb
|
|
226
230
|
- app/views/cms/posts_form.erb
|
|
@@ -235,6 +239,7 @@ files:
|
|
|
235
239
|
- app/views/index.erb
|
|
236
240
|
- app/views/layout.erb
|
|
237
241
|
- app/views/login.erb
|
|
242
|
+
- app/views/maintenance.erb
|
|
238
243
|
- app/views/page.erb
|
|
239
244
|
- app/views/portfolio.erb
|
|
240
245
|
- app/views/post.erb
|
|
@@ -255,6 +260,8 @@ files:
|
|
|
255
260
|
- db/data.sqlite3
|
|
256
261
|
- db/development.sqlite3
|
|
257
262
|
- db/migrations/20260502000000_create_products.rb
|
|
263
|
+
- db/migrations/20260507000000_create_activity_logs.rb
|
|
264
|
+
- db/migrations/20260507000001_fix_activity_logs_user_id.rb
|
|
258
265
|
- ofa
|
|
259
266
|
- public/css/cms.css
|
|
260
267
|
- public/images/logo.jpg
|
|
@@ -281,5 +288,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
281
288
|
requirements: []
|
|
282
289
|
rubygems_version: 3.6.7
|
|
283
290
|
specification_version: 4
|
|
284
|
-
summary:
|
|
291
|
+
summary: Modern & Powerfull web application framework.
|
|
285
292
|
test_files: []
|