fullstack-admin 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/TODO.tasks CHANGED
@@ -2,7 +2,8 @@
2
2
  = Fullstack Admin Roadmap =
3
3
  ===========================
4
4
 
5
- - Fix positionable/_collection.html.erb not working
5
+ - Fix assets compilation issues (and lock assets-related gems to working versions only)
6
+ ✓ Fix positionable/_collection.html.erb not working
6
7
 
7
8
  - Has many nested:
8
9
  has_many_nested = has_many + accepts_nested_attributes_for + :allow_destroy => true [CORE]
@@ -33,17 +34,17 @@
33
34
 
34
35
  ✓ Ckeditor: upload assets to S3 according to app.config
35
36
 
36
- - Localizable models
37
- - Create Localized module: a model is localizable if has a :locale field (cms?)
38
- - Add a scope to Localized to find models within the current locale (cms?)
37
+ Localizable models
38
+ Create Localized module: a model is localizable if has a :locale field (cms?)
39
+ Add a scope to Localized to find models within the current locale (cms?)
39
40
 
40
- - Create an option to specify the admin_locale
41
- - add a before filter to Admin::BaseController that uses the default locale for the admin
42
- - Split localized models index into tabs (either through ajax?)
43
- - Let the programmer decide a default locale and a set of available locales
44
- - Translations for locale codes
41
+ Create an option to specify the admin_locale
42
+ add a before filter to Admin::BaseController that uses the default locale for the admin
43
+ Split localized models index into tabs (either through ajax?)
44
+ Let the programmer decide a default locale and a set of available locales
45
+ Translations for locale codes
45
46
 
46
- - Form for accepts_nested_attributes_for and has_one
47
+ - Form for accepts_nested_attributes_for and has_one (NOT TESTED?)
47
48
  - Optional tracking of author/updaters for every model (CMS?)
48
49
 
49
50
  ==================
@@ -55,9 +56,9 @@
55
56
  - find alternative to sortable
56
57
 
57
58
  - Multiple scopes
58
- - Tags input with chosen
59
+ - Tags input with select2
59
60
 
60
- - Alternated fields:
61
+ - Conditional fields and Virtual Fields:
61
62
  = Group of fields that can be setted exclusively
62
63
  = eg
63
64
  = field :age
@@ -65,5 +66,10 @@
65
66
 
66
67
  = virtual_field :link_method, :in => %W(page external)
67
68
 
68
- = belongs_to :related_page, :meaningful_if => :link_method.eq("page")
69
- = field :url, :meaningful_if => :link_method.eq("external")
69
+ = belongs_to :related_page, :meaningful_when => :link_method.eq("page")
70
+ = field :url, :meaningful_when => :link_method.eq("external")
71
+
72
+ - Validation DSL
73
+ = field :name, :string, :required
74
+ = field :email, :required, :unique
75
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.2
@@ -1,4 +1,5 @@
1
1
  /*
2
+ *= require admin/login
2
3
  *= require support/base
3
4
  *= require support/bootstrap
4
5
  *= require support/forms
@@ -13,15 +14,113 @@
13
14
  }
14
15
  }
15
16
 
17
+ input {
18
+ -moz-box-sizing: border-box;
19
+ -webkit-box-sizing: border-box;
20
+ box-sizing: border-box;
21
+ width: 100%;
22
+ height: auto !important;
23
+ }
24
+
25
+ /* ========= */
26
+ /* = Icons = */
27
+ /* ========= */
28
+
29
+ .icon-white, .nav-tabs > .active > a > [class^="icon-"], .nav-tabs > .active > a > [class*=" icon-"], .nav-pills > .active > a > [class^="icon-"], .nav-pills > .active > a > [class*=" icon-"], .nav-list > .active > a > [class^="icon-"], .nav-list > .active > a > [class*=" icon-"], .navbar-inverse .nav > .active > a > [class^="icon-"], .navbar-inverse .nav > .active > a > [class*=" icon-"], .dropdown-menu > li > a:hover > [class^="icon-"], .dropdown-menu > li > a:hover > [class*=" icon-"], .dropdown-menu > .active > a > [class^="icon-"], .dropdown-menu > .active > a > [class*=" icon-"] {
30
+ background-image: url("/img/glyphicons-halflings-white.png");
31
+ }
32
+
33
+ /* ========== */
34
+ /* = Navbar = */
35
+ /* ========== */
36
+
37
+ .navbar .nav li.dropdown > .dropdown-toggle .caret {
38
+ border-top-color: white;
39
+ border-bottom-color: white;
40
+ }
41
+
42
+ .navbar-inner {
43
+ background: #3993ba;
44
+ background: -moz-linear-gradient(top, #3993ba 0%, #067ead 100%);
45
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#3993ba), color-stop(100%,#067ead));
46
+ background: -webkit-linear-gradient(top, #3993ba 0%,#067ead 100%);
47
+ background: -o-linear-gradient(top, #3993ba 0%,#067ead 100%);
48
+ background: -ms-linear-gradient(top, #3993ba 0%,#067ead 100%);
49
+ background: linear-gradient(top, #3993ba 0%,#067ead 100%);
50
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3993ba', endColorstr='#067ead',GradientType=0 );
51
+ -webkit-box-shadow: none;
52
+ -moz-box-shadow: none;
53
+ box-shadow: none;
54
+
55
+ }
56
+
57
+ .navbar .nav > li > a {
58
+ color: #c1dce7;
59
+ }
60
+
61
+ .navbar .nav > li:hover > a {
62
+ color:#fff;
63
+ }
64
+
65
+ .navbar .nav .active > a, .navbar .nav .active > a:hover, .navbar .nav li.dropdown.open > .dropdown-toggle {
66
+ background: #206484;
67
+ color: #fff;
68
+ }
69
+
70
+ .navbar .divider-vertical {
71
+ background-color:#2078A1;
72
+ border-color:#3497C2;
73
+ }
74
+
75
+ .navbar .divider-vertical {
76
+ border-left-color: #2078A1;
77
+ border-right-color: #3497C2;
78
+ }
79
+
80
+ .dropdown-menu li > a:hover, .dropdown-menu .active > a,
81
+ .dropdown-menu .active > a:hover,
82
+ .nav-list > .active > a, .nav-list > .active > a:hover {
83
+ background: #48a6d2 !important;
84
+ }
85
+
86
+ .table thead th {background-color:#ebf2f6 !important}
87
+ .dataTables_wrapper th.sorting_asc,.dataTables_wrapper th.sorting_desc {background-color:#d4e3eb !important}
88
+
89
+
16
90
  #left-aside .nav {
17
91
  padding-bottom: 180px;
18
92
  }
19
93
 
94
+ .navbar .nav > li > a {
95
+ padding-top: 10px;
96
+ }
97
+ .navbar .nav > li > a {
98
+ text-shadow: none;
99
+ padding: 9px 10px 11px;
100
+ }
101
+ .navbar .nav > li > a {
102
+ color: #C1DCE7;
103
+ }
104
+
105
+ .navbar .brand {
106
+ width: 200px;
107
+ font: 100 16px/16px 'PT Sans', sans-serif;
108
+ text-decoration: none;
109
+ color: #c1dce7;
110
+ text-shadow: none;
111
+ padding: 10px 20px 0;
112
+ }
113
+
114
+ .navbar .brand:hover {
115
+ color: #fff;
116
+ }
117
+
20
118
  .sidenav > li:first-child {
21
119
  -webkit-border-radius: 6px 6px 0 0;
22
120
  -moz-border-radius: 6px 6px 0 0;
23
121
  border-radius: 6px 6px 0 0;
24
122
  }
123
+
25
124
  .sidenav > li {
26
125
  display: block;
27
126
  margin: 0 0 -1px;
@@ -31,7 +130,6 @@ margin-left: -15px;
31
130
  margin-right: -15px;
32
131
  }
33
132
 
34
-
35
133
  .thumbnails > li {
36
134
  float: left;
37
135
  margin-left: 0 !important;
@@ -58,4 +156,217 @@ margin-right: -15px;
58
156
  background: url(/assets/iconic/gray/magnifying_glass_12x12.png) no-repeat left center;
59
157
  margin-left: 7px !important;
60
158
  padding-left: 12px !important;
159
+ }
160
+
161
+ /* ======= */
162
+ /* = Box = */
163
+ /* ======= */
164
+
165
+ .box {
166
+ -webkit-box-shadow: 0px 1px 2px 0px #EFEFEF;
167
+ -moz-box-shadow: 0px 1px 2px 0px #EFEFEF;
168
+ box-shadow: 0px 1px 2px 0px #EFEFEF;
169
+ margin-bottom: 20px;
170
+ }
171
+
172
+ .box-header, .box-content, .box-footer {
173
+ padding: 20px;
174
+ }
175
+
176
+ .box-header {
177
+ height: 32px;
178
+ line-height: 32px;
179
+ border: 1px solid #DDD;
180
+ padding: 0 10px;
181
+ background: #FBFBFB;
182
+ background: -moz-linear-gradient(top, #FBFBFB 0%, #F1F1F1 100%);
183
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#FBFBFB), color-stop(100%,#F1F1F1));
184
+ background: -webkit-linear-gradient(top, #FBFBFB 0%,#F1F1F1 100%);
185
+ background: -o-linear-gradient(top, #FBFBFB 0%,#F1F1F1 100%);
186
+ background: -ms-linear-gradient(top, #FBFBFB 0%,#F1F1F1 100%);
187
+ background: linear-gradient(top, #FBFBFB 0%,#F1F1F1 100%);
188
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fbfbfb', endColorstr='#f1f1f1',GradientType=0 );
189
+ font-weight: 700;
190
+ color: #666;
191
+ font-size: 11px;
192
+ }
193
+
194
+
195
+ .box-content {
196
+ padding: 10px;
197
+ border: 1px solid #DDD;
198
+ border-top: none;
199
+ }
200
+
201
+ .box-content:last-child {
202
+ margin-bottom: 0;
203
+ padding-bottom: 0;
204
+ }
205
+
206
+ .box-footer {
207
+ border: 1px solid #DDD;
208
+ padding: 8px 10px;
209
+ background: #F1F1F1;
210
+ border-top: none;
211
+ }
212
+
213
+ .box-footer.align-right {
214
+ text-align: right;
215
+ }
216
+
217
+ fieldset.box-content {
218
+ padding-top: 20px;
219
+ }
220
+
221
+ /* ========== */
222
+ /* = Layout = */
223
+ /* ========== */
224
+
225
+ a, button, input {
226
+ outline: none !important;
227
+ }
228
+
229
+ a {
230
+ color: #08C;
231
+ text-decoration: none;
232
+ }
233
+
234
+ body {
235
+ padding-top: 60px;
236
+ }
237
+
238
+ .main {
239
+ background: white;
240
+ border-left: 1px solid transparent;
241
+ margin-left: 240px;
242
+ }
243
+
244
+
245
+ /* =============== */
246
+ /* = Page Header = */
247
+ /* =============== */
248
+
249
+ .resource_updated_at {
250
+ font: italic 12px/32px Arial, Helvetica, sans-serif;
251
+ color: #888;
252
+ }
253
+
254
+ .page-header h1, h1.page-header{
255
+ line-height: 34px;
256
+ font-size: 18px;
257
+ margin-top: 0;
258
+ }
259
+ .page-header h1 {
260
+ margin-bottom: 0;
261
+ }
262
+
263
+ #center > .page-header {
264
+ margin-top: 0;
265
+ margin-bottom: 20px;
266
+ padding: 0;
267
+ }
268
+
269
+ .index-actions {
270
+ float:right;
271
+ }
272
+
273
+
274
+ /* =========== */
275
+ /* = Sidebar = */
276
+ /* =========== */
277
+
278
+ .sidebar {
279
+ position: fixed;
280
+ top: 40px;
281
+ left: 0;
282
+ margin-left: 0;
283
+ padding-top: 20px;
284
+ width: 240px;
285
+ background: #f1f1f1;
286
+ border-right: 1px solid #ccc;
287
+ height: 100%;
288
+ font: 13px/18px "Helvetica Neue",Helvetica,Arial,sans-serif;
289
+ }
290
+
291
+ .sidebar .accordion {
292
+ border-top: 1px solid #CCC;
293
+ margin-bottom: 20px;
294
+ }
295
+
296
+ .sidebar .accordion-heading a:hover {
297
+ background-color: #CFCFCF;
298
+ }
299
+
300
+ .sidebar .accordion-heading {
301
+ text-shadow: 1px 1px 0 #EFEFEF;
302
+ background: #E0E0E0;
303
+ -webkit-box-shadow: inset 0px 1px 0px 0px #ECECEC;
304
+ box-shadow: inset 0px 1px 0px 0px #ECECEC;
305
+ }
306
+
307
+ .sidebar .accordion-inner {
308
+ border-top: 1px solid #CCC;
309
+ background: #FAFAFA;
310
+ }
311
+
312
+ .sidebar .accordion-group .accordion-heading a {
313
+ color: #222;
314
+ }
315
+
316
+ .sidebar .accordion-group .active a {
317
+ color: white;
318
+ }
319
+
320
+ .nav-list > li > a, .dropdown-menu li a {
321
+ -webkit-border-radius: 4px;
322
+ -moz-border-radius: 4px;
323
+ -ms-border-radius: 4px;
324
+ border-radius: 4px;
325
+ }
326
+
327
+ .dropdown-menu li > a:hover, .dropdown-menu .active > a, .dropdown-menu .active > a:hover, .nav-list > .active > a, .nav-list > .active > a:hover {
328
+ background: #48A6D2 !important;
329
+ }
330
+
331
+ .nav > li > a:hover {
332
+ text-decoration: none;
333
+ background-color: #EEE;
334
+ }
335
+
336
+ .sidebar .accordion-group a {
337
+ color: #222;
338
+ text-decoration: none!important;
339
+ }
340
+
341
+ .sidebar .accordion-group {
342
+ -webkit-border-radius: 0;
343
+ -moz-border-radius: 0;
344
+ border-radius: 0;
345
+ margin-bottom: 0;
346
+ border-color: #CCC;
347
+ border-style: solid;
348
+ border-width: 0 0 1px;
349
+ }
350
+
351
+ .accordion-heading .accordion-toggle {
352
+ display: block;
353
+ padding: 7px 15px;
354
+ }
355
+
356
+ .accordion-toggle {
357
+ -webkit-transition: background-color 0.2s ease-in-out;
358
+ -moz-transition: background-color 0.2s ease-in-out;
359
+ -o-transition: background-color 0.2s ease-in-out;
360
+ transition: background-color 0.2s ease-in-out;
361
+ }
362
+ .accordion-toggle {
363
+ cursor: pointer;
364
+ }
365
+
366
+ /* ================ */
367
+ /* = Filter Input = */
368
+ /* ================ */
369
+
370
+ .filter-inputs {
371
+ margin: 0;
61
372
  }
@@ -0,0 +1,54 @@
1
+ .login-box {
2
+ width: 380px;
3
+ margin: auto;
4
+ padding: 0;
5
+ position: relative;
6
+ top: 50%;
7
+ background: white;
8
+ border: 1px solid #CCC;
9
+ -webkit-border-radius: 6px;
10
+ -moz-border-radius: 6px;
11
+ -ms-border-radius: 6px;
12
+ border-radius: 6px;
13
+ -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
14
+ -moz-box-shadow: 0 0 6px rgba(0,0,0,0.2);
15
+ -ms-box-shadow: 0 0 6px rgba(0,0,0,0.2);
16
+ box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
17
+ }
18
+
19
+ .login-box-header, .login-box-content, .form-actions.login-box-footer {
20
+ padding: 20px;
21
+ margin: 0;
22
+ }
23
+
24
+ .login-box-header {
25
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
26
+ font: 100 18px/42px 'PT Sans', sans-serif;
27
+ height: 42px;
28
+ padding: 0 20px;
29
+ background: #E0E0E0;
30
+ border-bottom: 1px solid #CCC;
31
+ -moz-border-radius-topleft: 6px;
32
+ -moz-border-radius-topright: 6px;
33
+ -moz-border-radius-bottomright: 0px;
34
+ -moz-border-radius-bottomleft: 0px;
35
+ -webkit-border-radius: 6px 6px 0px 0px;
36
+ border-radius: 6px 6px 0px 0px;
37
+ font-size: 15px;
38
+ }
39
+
40
+ .login-box input[type="text"], .login-box input[type="password"] {
41
+ width: 298px;
42
+ }
43
+
44
+ .login-box-footer {
45
+ padding: 12px 20px;
46
+ border-top: 1px solid #E7E7E7;
47
+ background: #F7F7F7;
48
+ -moz-border-radius-topleft: 0px;
49
+ -moz-border-radius-topright: 0px;
50
+ -moz-border-radius-bottomright: 6px;
51
+ -moz-border-radius-bottomleft: 6px;
52
+ -webkit-border-radius: 0px 0px 6px 6px;
53
+ border-radius: 0px 0px 6px 6px;
54
+ }
@@ -8,7 +8,7 @@
8
8
  .badge.badge-mini {
9
9
  padding: 0 5px;
10
10
  font-size: 9px;
11
- display: inline-block;}
11
+ display: inline-block;
12
12
  line-height: 18px;
13
13
  }
14
14
 
@@ -1,17 +1,13 @@
1
1
  class Admin::BaseController < ApplicationController
2
- rescue_from Checkin::AccessDenied, :with => :rescue_access_denied
3
-
2
+ before_filter :require_login
3
+ before_filter :fetch_current_resource
4
+
4
5
  layout 'admin'
5
- authorize(:scope => :admin)
6
6
 
7
7
  protected
8
8
 
9
- def rescue_access_denied
10
- if subject.guest?
11
- redirect_to new_admin_session_path
12
- else
13
- render :text => "Not Authorized", :status => 403
14
- end
9
+ def not_authenticated
10
+ redirect_to new_admin_session_url, :alert => "First login to access this page."
15
11
  end
16
12
 
17
13
  class << self
@@ -27,19 +23,24 @@ class Admin::BaseController < ApplicationController
27
23
  :current_resource_class,
28
24
  :current_resource,
29
25
  :current_collection,
30
- :title_column
26
+ :title_column,
27
+ :subject
31
28
 
32
29
 
30
+ def subject
31
+ @subject ||= ::Admin::SubjectModelAdapter.new(current_user)
32
+ end
33
+
33
34
  def current_resource_class
34
- @current_resource_class ||= controller_name.singularize.camelize.constantize
35
+ @current_resource_class ||= controller_name.singularize.camelize.constantize rescue nil
35
36
  end
36
37
 
37
38
  def resource_name
38
- current_resource_class.name.demodulize.underscore
39
+ current_resource_class && current_resource_class.name.demodulize.underscore
39
40
  end
40
41
 
41
42
  def collection_name
42
- resource_name.pluralize
43
+ resource_name.try(:pluralize)
43
44
  end
44
45
 
45
46
  alias :singular_name :resource_name
@@ -59,5 +60,9 @@ class Admin::BaseController < ApplicationController
59
60
  @_title_columns[model] ||= ( model.column_names.map{ |c| c.to_s } & %W(title name label browser_title seo_title seo_name key claim email) ).first
60
61
  end
61
62
 
63
+ def fetch_current_resource
64
+ return if !params[:id] || current_resource
65
+ instance_variable_set("@#{resource_name}", current_resource_class.find(params[:id]))
66
+ end
62
67
 
63
- end
68
+ end
@@ -1,19 +1,27 @@
1
1
  class Admin::SessionsController < ApplicationController
2
- layout 'admin'
2
+ layout 'login'
3
+
4
+ def new
5
+ @user = Superuser.new
6
+ end
3
7
 
4
8
  def create
5
- user = login(params[:email], params[:password], params[:remember_me])
6
- if user
7
- redirect_back_or_to root_url, :notice => "Logged in!"
8
- else
9
- flash.now.alert = "Email or password was invalid"
10
- render :new
9
+ respond_to do |format|
10
+ if @user = login(params[:username],params[:password])
11
+ format.html { redirect_back_or_to("/", :notice => I18n.t("signed_in", :scope => "fullstack.admin", :default => 'Signed in successfully.')) }
12
+ format.xml { render :xml => @user, :status => :created, :location => @user }
13
+ else
14
+ format.html { flash.now[:alert] = I18n.t("login_failed", :scope => "fullstack.admin", :default => 'Login failed.'); render :action => "new" }
15
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
16
+ end
11
17
  end
12
18
  end
13
-
19
+
14
20
  def destroy
15
21
  logout
16
- redirect_to root_url, :notice => "Logged out!"
22
+ redirect_to("/", :notice => I18n.t("signed_out", :scope => "fullstack.admin", :default => 'Signed out successfully.'))
17
23
  end
18
24
 
19
- end
25
+ end
26
+
27
+
@@ -0,0 +1,57 @@
1
+ class Admin::SubjectModelAdapter
2
+ attr_accessor :subject_model
3
+
4
+ def initialize(subject_model)
5
+ @subject_model = subject_model
6
+ end
7
+
8
+ def guest?
9
+ !subject_model
10
+ end
11
+
12
+ def logged_in?
13
+ !!subject_model
14
+ end
15
+
16
+ def owner?(object)
17
+ object && ( object.respond_to?(:author) && ( subject_model == object.author ) ) || ( object.respond_to?(:owner) && ( subject_model == object.owner ) )
18
+ end
19
+ alias :own? :owner?
20
+
21
+ def administrator?
22
+ true
23
+ end
24
+
25
+ def can?(*args)
26
+ true
27
+ end
28
+
29
+ def can_edit?(*args)
30
+ true
31
+ end
32
+
33
+ def can_create?(*args)
34
+ true
35
+ end
36
+
37
+ def can_destroy?(*args)
38
+ true
39
+ end
40
+
41
+ def can_new?(*args)
42
+ true
43
+ end
44
+
45
+ def can_update?(*args)
46
+ true
47
+ end
48
+
49
+ def can_sort?(*args)
50
+ true
51
+ end
52
+
53
+ def can_show?(*args)
54
+ true
55
+ end
56
+
57
+ end
@@ -1,12 +1,6 @@
1
1
  class Superuser < ActiveRecord::Base
2
2
  authenticates_with_sorcery!
3
-
4
- attr_accessible :email, :password, :password_confirmation
5
-
6
- validates_confirmation_of :password
7
- validates_presence_of :password, :on => :create
8
- validates_presence_of :email
9
- validates_uniqueness_of :email
10
- field :email
11
- field :password
12
- end
3
+ def has_role?(r)
4
+ true
5
+ end
6
+ end
@@ -1,13 +1,15 @@
1
- <% content_for :menu do -%>
1
+ <% content_for :menu do -%>
2
+
3
+ <%= nav(:class => 'float right') do %>
4
+ <li class="divider-vertical">
5
+ </li>
2
6
 
3
- <%= nav do %>
4
- <%= nav_item t('fullstack.admin.dashboard', :default => "Dashboard"), admin_root_path, :icon => "cog" %>
5
- <% end %>
7
+ <%= nav_item t('fullstack.admin.homepage', :default => "Homepage"), "/", :icon => "globe", :"icon_color" => :white %>
8
+ <li class="divider-vertical">
9
+ </li>
6
10
 
7
- <%= nav(:class => 'float right') do %>
8
- <%= nav_item t('fullstack.admin.homepage', :default => "Homepage"), "/", :icon => "home" %>
9
- <%= dropdown_nav_item t('fullstack.admin.account', :default => "Account"), :icon => "user" do %>
10
- <%= nav_item t('fullstack.admin.logout', :default => "Logout"), destroy_user_session_path, :method => :delete %>
11
+ <%= dropdown_nav_item t('fullstack.admin.account', :default => "Account"), :icon => "user", :"icon_color" => :white do %>
12
+ <%= nav_item t('fullstack.admin.logout', :default => "Logout"), admin_sessions_path, :method => :delete %>
11
13
  <% end %>
12
14
  <% end %>
13
15
 
@@ -15,19 +17,34 @@
15
17
 
16
18
  <% content_for :nav do -%>
17
19
 
18
- <div class="tabbable tabs-left float right">
19
- <%= nav_list :class => "nav-tabs" do %>
20
20
 
21
- <% Fullstack::Admin.resources.each do |rog| %>
22
- <% if rog.type == :group %>
23
- <%= nav_header t("fullstack.admin.groups.#{rog.name}", :default => rog.name.to_s.humanize) %>
24
-
25
- <% elsif rog.type == :resource %>
26
- <%= nav_item t("fullstack.admin.resources.#{rog.name}", :default => rog.name.to_s.humanize), [:admin, rog.name] %>
27
- <% end %>
28
- <% end %>
29
-
30
- <% end %>
31
- </div>
21
+ <div class="sidebar">
22
+ <div class="sidebar-inner">
23
+ <div class="accordion sidebar-accordion" id="sidebar-accordion">
24
+ <% i = 0 %>
25
+ <% Fullstack::Admin.grouped_resources.each do |group, resources| %>
26
+ <div class="accordion-group">
27
+ <div class="accordion-heading">
28
+ <a href="#sidebar_group_collapsable_<%= i+=1 %>" data-parent="#sidebar-accordion" data-toggle="collapse" class="accordion-toggle">
29
+ <i class="icon-<%= group.icon %>"></i> <%= t("fullstack.admin.groups.#{group.name}", :default => group.name.to_s.humanize) %>
30
+ <% if resources.map(&:name).include?(plural_name) %>
31
+ <i class="icon-chevron-left float right" style="opacity: 0.4;"></i>
32
+ <% end %>
33
+ </a>
34
+ </div>
35
+ <div class="accordion-body collapse <%= resources.map(&:name).include?(plural_name) ? 'in' : '' %>" id="sidebar_group_collapsable_<%= i %>">
36
+ <div class="accordion-inner">
37
+ <ul class="nav nav-list">
38
+ <% resources.each do |resource| %>
39
+ <%= nav_item t("fullstack.admin.resources.#{resource.name}", :default => resource.name.to_s.humanize), [:admin, resource.name] %>
40
+ <% end %>
41
+ </ul>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <% end %>
46
+ </div>
47
+ </div>
48
+ </div>
32
49
 
33
- <% end -%>
50
+ <% end %>
@@ -1,10 +1,43 @@
1
+ <div class="page-header">
2
+ <% unless partial?('collection')%>
3
+ <div class="mb1 index-actions">
4
+ <% if subject.can_create?(collection_name) && controller.action_methods.include?("new") %>
5
+ <%= button t('fullstack.admin.new', :default => "New"),
6
+ send("new_admin_#{resource_name}_path"),
7
+ :type => :primary, :icon => :plus, :icon_color => :white %>
8
+ <% end %>
9
+
10
+ <% if subject.can_sort?(collection_name) && positionable?(current_collection.klass) %>
11
+ <%= button t('fullstack.admin.sort', :default => "Sort"),
12
+ send("admin_positionables_path", :type => resource_name)
13
+ %>
14
+
15
+ <% end %>
16
+
17
+ <%= button t('fullstack.admin.delete', :default => "Delete"),
18
+ 'javascript:void(0)', :class => "toggle-delete",
19
+ :icon => :trash %>
20
+ </div>
21
+
22
+ <% end %>
23
+
24
+ <h1>
1
25
  <% if content_for?(:title) %>
2
- <div class="page-header"><h1><%= yield(:title) %></h1></div>
26
+ <%= yield(:title) %>
27
+
3
28
  <% elsif @title %>
4
- <div class="page-header"><h1><%= @title %></h1></div>
29
+
30
+ <%= @title %>
31
+
5
32
  <% else -%>
6
- <div class="page-header"><h1><%= t(collection_name, :scope => "fullstack.admin.resources") %></h1></div>
33
+
34
+ <%= t(collection_name, :scope => "fullstack.admin.resources") %>
35
+
7
36
  <% end -%>
37
+ </h1>
38
+
39
+
40
+ </div>
8
41
 
9
42
  <% if partial?('collection') %>
10
43
  <%= render :partial => "collection",
@@ -15,24 +48,7 @@
15
48
  %>
16
49
  <% else %>
17
50
 
18
- <div class="mb1">
19
- <% if subject.can_create?(collection_name) && controller.action_methods.include?("new") %>
20
- <%= button t('fullstack.admin.new', :default => "New"),
21
- send("new_admin_#{resource_name}_path"),
22
- :type => :primary, :icon => :plus, :icon_color => :white %>
23
- <% end %>
24
-
25
- <% if subject.can_sort?(collection_name) && positionable?(current_collection.klass) %>
26
- <%= button t('fullstack.admin.sort', :default => "Sort"),
27
- send("admin_positionables_path", :type => resource_name)
28
- %>
29
-
30
- <% end %>
31
-
32
- <%= button t('fullstack.admin.delete', :default => "Delete"),
33
- 'javascript:void(0)', :class => "toggle-delete",
34
- :icon => :trash %>
35
- </div>
51
+
36
52
 
37
53
 
38
54
  <table class="table table-bordered table-striped index-table">
@@ -56,16 +72,18 @@
56
72
  <% content_for :aside do -%>
57
73
 
58
74
  <% if (!@skip_filter) && (partial?('filter') || title_column(current_resource_class) || has_timestamps?(current_resource_class)) %>
59
- <div class="well">
60
- <div class="mb1">
61
- <h4><%= t('fullstack.admin.filter', :default => "Filter") %></h4>
75
+ <div class="box">
76
+ <div class="box-header">
77
+ <%= t('fullstack.admin.filter', :default => "Filter") %>
62
78
  </div>
79
+
63
80
  <%= admin_form_for @search, :url => self.send("admin_#{collection_name}_path") , :html => {:method => :get} do |f| %>
81
+ <div class="box-content">
64
82
 
65
83
  <% if partial?('filter') %>
66
84
  <%= render :partial => "filter", :locals => {:f => f} %>
67
85
  <% else %>
68
- <%= f.inputs do %>
86
+ <%= f.inputs :class => "filter-inputs" do %>
69
87
 
70
88
  <% if tc = title_column(current_resource_class) %>
71
89
  <%= f.input :"#{tc}_contains" %>
@@ -78,9 +96,10 @@
78
96
  <% end %>
79
97
 
80
98
  <% end %>
81
- <%= f.actions do %>
82
- <%= f.action t('fullstack.admin.filter', :default => "Filter"), :as => :button %>
83
- <% end %>
99
+ </div>
100
+ <div class="box-footer align-right">
101
+ <%= f.action t('fullstack.admin.filter', :default => "Filter"), :as => :button %>
102
+ </div>
84
103
  <% end %>
85
104
  </div>
86
105
  <% end %>
@@ -0,0 +1,34 @@
1
+ <%= form_tag admin_sessions_path, :method => :post, :class => "login-box" do %>
2
+ <div class="login-box-header">
3
+ Login
4
+ </div>
5
+ <div class="login-box-content">
6
+
7
+ <% [:notice, :error, :alert].each do |sym| %>
8
+
9
+ <% if flash[sym].present? %>
10
+ <div class="alert alert-info alert-login ">
11
+ <%= flash[sym] %>
12
+ </div>
13
+ <% flash.discard(sym) %>
14
+ <% end %>
15
+
16
+ <% end %>
17
+
18
+
19
+ <div class="input-prepend">
20
+ <span class="add-on"><i class="icon-user"></i></span>
21
+ <%= text_field_tag :username, nil, :placeholder => I18n.t("username", :scope => "helpers.label", :default => "Username") %>
22
+ </div>
23
+
24
+ <div class="input-prepend">
25
+ <span class="add-on"><i class="icon-lock"></i></span>
26
+ <%= password_field_tag :password, nil, :placeholder => I18n.t("password", :scope => "helpers.label", :default => "Password") %>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="actions form-actions login-box-footer">
31
+ <%= submit_tag "Login", :class => "btn btn-primary" %>
32
+ </div>
33
+
34
+ <% end %>
@@ -12,25 +12,23 @@
12
12
  <%= render :partial => 'admin/nav' %>
13
13
 
14
14
  <%= navbar :fluid => true, :fixed => :top do %>
15
- <%= brand "#{app_name} Admin", admin_root_path %>
15
+ <a class="brand" href="/admin">
16
+ <i class="icon-home icon-white"></i> <%= "#{app_name} Admin" %></a>
16
17
  <%= yield :menu %>
17
18
  <% end %>
18
19
 
19
20
  <%= yield :crumbs %>
21
+ <%= yield :nav %>
20
22
 
21
23
  <%= container :fluid => true, :class => "main" do %>
22
- <%= row do %>
24
+ <%= row do %>
23
25
 
24
- <%= span(2, :id => "left-aside") do %>
25
- <%= yield :nav %>
26
- <% end %>
27
-
28
- <%= span(content_for?(:aside) ? 6 : 10, :id => "center") do %>
26
+ <%= span(content_for?(:aside) ? 9 : 12, :id => "center") do %>
29
27
  <%= yield %>
30
28
  <% end %>
31
29
 
32
30
  <% if content_for?(:aside) %>
33
- <%= span(4, :id => "left-aside") do %>
31
+ <%= span(3, :id => "right-aside") do %>
34
32
  <%= yield :aside %>
35
33
  <% end %>
36
34
  <% end %>
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="it">
3
+ <head>
4
+ <%= csrf_meta_tags %>
5
+ <title><%= ( @title ? "#{@title} - " : "" ) + "#{app_name} Admin" %></title>
6
+
7
+ <%= stylesheet_link_tag 'admin/admin' %>
8
+ <%= yield :stylesheets %>
9
+ </head>
10
+
11
+ <body>
12
+ <%= yield %>
13
+ </body>
14
+ </html>
15
+
@@ -30,6 +30,10 @@ it:
30
30
  choose_a_file: "Carica"
31
31
  change: "Cambia"
32
32
  preview: "Anteprima"
33
+ signed_in: 'Login effettuato con successo.'
34
+ signed_out: 'Logout effettuato con successo.'
35
+ login_failed: 'Nome utente o password non validi.'
36
+
33
37
 
34
38
  form:
35
39
  correct_these_errors_and_retry: "Correggi questi errori e riprova"
@@ -65,4 +65,5 @@ it:
65
65
  category: "Categoria"
66
66
  post_code: "Codice postale"
67
67
  kind: "Tipo"
68
- state: "Provincia"
68
+ state: "Provincia"
69
+ password_confirmation: "Conferma Password"
@@ -3,6 +3,10 @@ Rails.application.routes.draw do
3
3
  mount Ckeditor::Engine => '/ckeditor'
4
4
 
5
5
  namespace :admin do
6
+ resources :sessions, :only => [:new, :create] do
7
+ match :destroy, :via => :delete, :on => :collection
8
+ end
9
+
6
10
  resources :positionables, :only => [:index] do
7
11
  post :sort, :on => :collection
8
12
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "fullstack-admin"
8
- s.version = "0.2.1"
8
+ s.version = "0.2.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["mcasimir"]
12
- s.date = "2012-11-04"
12
+ s.date = "2012-11-05"
13
13
  s.description = "Administration interface framework for fullstack"
14
14
  s.email = "maurizio.cas@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -939,6 +939,7 @@ Gem::Specification.new do |s|
939
939
  "app/assets/javascripts/support/plupload.js.coffee",
940
940
  "app/assets/javascripts/support/uploads.js.coffee",
941
941
  "app/assets/stylesheets/admin/base.css",
942
+ "app/assets/stylesheets/admin/login.css",
942
943
  "app/assets/stylesheets/support/ajax_loading.css",
943
944
  "app/assets/stylesheets/support/base.css",
944
945
  "app/assets/stylesheets/support/bootstrap-workarounds.css",
@@ -952,6 +953,7 @@ Gem::Specification.new do |s|
952
953
  "app/controllers/admin/responder.rb",
953
954
  "app/controllers/admin/scaffold_controller.rb",
954
955
  "app/controllers/admin/sessions_controller.rb",
956
+ "app/controllers/admin/subject_model_adapter.rb",
955
957
  "app/helpers/admin_form_helper.rb",
956
958
  "app/helpers/scaffold_helper.rb",
957
959
  "app/inputs/country_input.rb",
@@ -993,6 +995,7 @@ Gem::Specification.new do |s|
993
995
  "app/views/admin/base/new.html.erb",
994
996
  "app/views/admin/base/update.js.coffee",
995
997
  "app/views/admin/positionables/_collection.html.erb",
998
+ "app/views/admin/sessions/new.html.erb",
996
999
  "app/views/kaminari/_first_page.html.erb",
997
1000
  "app/views/kaminari/_gap.html.erb",
998
1001
  "app/views/kaminari/_last_page.html.erb",
@@ -1001,6 +1004,7 @@ Gem::Specification.new do |s|
1001
1004
  "app/views/kaminari/_paginator.html.erb",
1002
1005
  "app/views/kaminari/_prev_page.html.erb",
1003
1006
  "app/views/layouts/admin.html.erb",
1007
+ "app/views/layouts/login.html.erb",
1004
1008
  "config/initializers/formtastic_bootstrap_timeish_hack.rb",
1005
1009
  "config/locales/devise.en.yml",
1006
1010
  "config/locales/devise.views.en.yml",
@@ -61,7 +61,7 @@ module Fullstack
61
61
 
62
62
  # Group
63
63
  class Group < Entity
64
- attr_accessor :children, :name
64
+ attr_accessor :children, :name, :icon
65
65
 
66
66
  def initialize(name)
67
67
  @name = "#{name}"
@@ -128,6 +128,27 @@ module Fullstack
128
128
  end
129
129
 
130
130
  module_function :resources
131
+
132
+ def grouped_resources
133
+ if !@resource_groups
134
+ @resource_groups = {}
135
+ current_group = nil
136
+
137
+ resources.each do |rog|
138
+ if rog.type == :group
139
+ @resource_groups[rog] = []
140
+ current_group = rog
141
+ elsif current_group
142
+ @resource_groups[current_group] << rog
143
+ end
144
+ end
145
+ end
146
+ @resource_groups
147
+ end
148
+
149
+ module_function :grouped_resources
150
+
151
+
131
152
  end
132
153
  end
133
154
 
@@ -33,12 +33,13 @@ eos
33
33
 
34
34
 
35
35
  def users
36
+ generate "sorcery:install remember_me activity_logging brute_force_protection --model Superuser"
36
37
  generate "migration:from user"
37
38
  append_to_file "db/seeds.rb" do
38
39
  <<-eos
39
40
 
40
41
  if Rails.env.development?
41
- user = User.new( :email => "admin@example.com",
42
+ user = Superuser.new( :email => "admin@example.com",
42
43
  :password => "password" )
43
44
 
44
45
  user.skip_confirmation! if user.respond_to?(:skip_confirmation!)
@@ -64,7 +65,7 @@ eos
64
65
  eos
65
66
  route(src)
66
67
 
67
- route("\n devise_for :users\n")
68
+ #route("\n devise_for :users\n")
68
69
  end
69
70
 
70
71
  protected
@@ -13,7 +13,7 @@ class UserSubject < Checkin::Subject
13
13
  end
14
14
 
15
15
  role :administrator, :require => :logged_in, :alias => :admin do
16
- subject_model.has_role?(:administrator)
16
+ subject_model && subject_model.is_a?(Superuser)
17
17
  end
18
18
 
19
19
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fullstack-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-04 00:00:00.000000000 Z
12
+ date: 2012-11-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -1149,6 +1149,7 @@ files:
1149
1149
  - app/assets/javascripts/support/plupload.js.coffee
1150
1150
  - app/assets/javascripts/support/uploads.js.coffee
1151
1151
  - app/assets/stylesheets/admin/base.css
1152
+ - app/assets/stylesheets/admin/login.css
1152
1153
  - app/assets/stylesheets/support/ajax_loading.css
1153
1154
  - app/assets/stylesheets/support/base.css
1154
1155
  - app/assets/stylesheets/support/bootstrap-workarounds.css
@@ -1162,6 +1163,7 @@ files:
1162
1163
  - app/controllers/admin/responder.rb
1163
1164
  - app/controllers/admin/scaffold_controller.rb
1164
1165
  - app/controllers/admin/sessions_controller.rb
1166
+ - app/controllers/admin/subject_model_adapter.rb
1165
1167
  - app/helpers/admin_form_helper.rb
1166
1168
  - app/helpers/scaffold_helper.rb
1167
1169
  - app/inputs/country_input.rb
@@ -1203,6 +1205,7 @@ files:
1203
1205
  - app/views/admin/base/new.html.erb
1204
1206
  - app/views/admin/base/update.js.coffee
1205
1207
  - app/views/admin/positionables/_collection.html.erb
1208
+ - app/views/admin/sessions/new.html.erb
1206
1209
  - app/views/kaminari/_first_page.html.erb
1207
1210
  - app/views/kaminari/_gap.html.erb
1208
1211
  - app/views/kaminari/_last_page.html.erb
@@ -1211,6 +1214,7 @@ files:
1211
1214
  - app/views/kaminari/_paginator.html.erb
1212
1215
  - app/views/kaminari/_prev_page.html.erb
1213
1216
  - app/views/layouts/admin.html.erb
1217
+ - app/views/layouts/login.html.erb
1214
1218
  - config/initializers/formtastic_bootstrap_timeish_hack.rb
1215
1219
  - config/locales/devise.en.yml
1216
1220
  - config/locales/devise.views.en.yml
@@ -1291,7 +1295,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
1291
1295
  version: '0'
1292
1296
  segments:
1293
1297
  - 0
1294
- hash: -697068176308442679
1298
+ hash: 445393833660587370
1295
1299
  required_rubygems_version: !ruby/object:Gem::Requirement
1296
1300
  none: false
1297
1301
  requirements: