summaryrefslogtreecommitdiff
path: root/app/views/admin
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-12 23:42:43 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-12 23:42:43 +0200
commitb271648f89cba7cafafa1b73b1658b1c1bc0e4b0 (patch)
tree567302b1aa6add08b46e0be028a0972f76c52abc /app/views/admin
parentcf93acb8ad44ba9cd486e8f6457d9fd9fbc041cc (diff)
Rebuild the admin dashboard: icon nav, search, signposts, widgets
Replaces the old admin#index wizard -- accreted over years, never designed as a whole -- with the dashboard settled on this session: a three-icon nav (dashboard/search/log out, no locale selector), a nodes-first search bar, four task signposts, and two symmetric widgets (drafts/autosaves, recent changes) with a quiet housekeeping row beneath them. Node.recently_changed now filters and orders by the head page's own updated_at instead of the node's blanket timestamp, so a lock/unlock cycle with no actual publish no longer surfaces here, and the original publisher is no longer misattributed to someone else's housekeeping action. This also restores the "published" qualifier on each entry, which the query previously couldn't guarantee was true. @mynodes and its dedicated "My Work" table are retired along with the old wizard -- "Continue my work" is a link to the existing, already- correct NodesController#mine instead of a second, duplicate query. Its one dedicated test (dedup across multiple revisions by the same user) is ported to nodes_controller_test.rb, since mine already carries the same .distinct protection the old query did; it just had no test of its own until now.
Diffstat (limited to 'app/views/admin')
-rw-r--r--app/views/admin/_menu.html.erb14
-rw-r--r--app/views/admin/index.html.erb210
2 files changed, 73 insertions, 151 deletions
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index 4302987..970429d 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -1,9 +1,5 @@
1<%= language_selector %> 1<%= link_to icon("home", library: "tabler", "aria-hidden": true), admin_path, "aria-label": "Dashboard" %>
2<%= button_to 'Logout', logout_path, method: :delete %> 2<a href="#" onclick="admin_search.display_toggle(); return false;" aria-label="Search"><%= icon("search", library: "tabler", "aria-hidden": true) %></a>
3<%= link_to 'Overview', admin_path %> 3<%= button_to logout_path, method: :delete, aria: { label: "Log out" } do %>
4<a href="#" onclick="admin_search.display_toggle(); return false;">search</a> 4 <%= icon("logout", library: "tabler", "aria-hidden": true) %>
5<%= link_to 'Nodes', nodes_path, selected?('nodes') %> 5<% end %>
6<%= link_to 'Assets', assets_path, selected?('assets') %>
7<%= link_to 'Events', events_path, selected?('events') %>
8<%= link_to 'User', users_path, selected?('users') %>
9<%= link_to 'Navigation', menu_items_path, selected?('menu_items') %>
diff --git a/app/views/admin/index.html.erb b/app/views/admin/index.html.erb
index c67ccb3..bdb555e 100644
--- a/app/views/admin/index.html.erb
+++ b/app/views/admin/index.html.erb
@@ -1,155 +1,81 @@
1<div id="admin_wizard"> 1<h1>cccms dashboard</h1>
2 <h2>Quick links</h2> 2<div id="dashboard_search">
3 <div class="admin_wizard_button"><%= link_to 'Create post or page', new_node_path, :only_path => false %></div> 3 <%= form_tag admin_search_path, method: :get do %>
4 <div class="admin_wizard_button"><a href="#" id="admin_wizard_my_work" class="button">Continue my work</a></div> 4 <%= text_field_tag :search_term, nil, id: "dashboard_search_term", placeholder: "Search nodes and tags…", autocomplete: "off" %>
5 <div class="admin_wizard_button"><a href="#" id="admin_wizard_create_page" class="button">Add a page in the page tree</a></div> 5 <% end %>
6 <div class="admin_wizard_button"><%= link_to("Upload a new asset", new_asset_path, :only_path => false) %></div> 6 <div id="dashboard_search_results" class="search_results"></div>
7</div> 7</div>
8 8
9<p id="overview_toggle"> 9<div id="dashboard_signposts" class="button_row">
10 <a href="#" id="recent_changes_toggle" class="button">recent changes</a> 10 <%= link_to new_node_path, class: "action_button" do %>
11 <a href="#" id="my_work_toggle" class="button">my work</a> 11 <%= icon("plus", library: "tabler", "aria-hidden": true) %> Create post
12 <a href="#" id="current_drafts_toggle" class="button">current drafts (<%= @drafts_count %>)</a> 12 <% end %>
13 <a href="#" id="admin_sitemap_toggle" class="button">site map</a> 13 <%= link_to chapters_nodes_path, class: "action_button" do %>
14</p> 14 <%= icon("map-pin", library: "tabler", "aria-hidden": true) %> Find a chapter
15 <% end %>
16 <%= link_to new_asset_path, class: "action_button" do %>
17 <%= icon("upload", library: "tabler", "aria-hidden": true) %> Upload asset
18 <% end %>
19 <%= link_to sitemap_nodes_path, class: "action_button" do %>
20 <%= icon("list-tree", library: "tabler", "aria-hidden": true) %> Add a page in the page tree
21 <% end %>
22</div>
15 23
16<div id="recent_changes_table"> 24<div id="dashboard_widgets">
17 25 <div class="dashboard_widget">
18 <h2>Recent Changes</h2> 26 <h3>Drafts and autosaves</h3>
27 <ul>
28 <% @drafts.each do |node| %>
29 <li>
30 <div>
31 <%= link_to title_for_node(node), node_path(node) %>
32 <span class="field_hint"><%= link_to_path("#{node.unique_name} ↗", node.unique_name) %></span>
33 </div>
34 <span class="dashboard_widget_meta">
35 <% if (editor = node_last_editor(node)) %><%= editor %>, <% end %><%= relative_time_phrase(node.updated_at) %>
36 </span>
37 </li>
38 <% end %>
39 </ul>
40 <%= link_to "See all drafts →", drafts_nodes_path %>
41 </div>
19 42
20 <table class="node_table"> 43 <div class="dashboard_widget">
21 <tr class="header"> 44 <h3>Recent changes</h3>
22 <th class="node_id">ID</th> 45 <ul>
23 <th class="title">Title</th>
24 <th class="actions">Actions</th>
25 <th class="editor">Locked by</th>
26 <th class="revision">Rev.</th>
27 </tr>
28 <% @recent_changes.each do |node| %> 46 <% @recent_changes.each do |node| %>
29 <tr class="<%= cycle("even", "odd") %>"> 47 <li>
30 <td class="node_id"><%= node.id %></td> 48 <div>
31 <td class="title"> 49 <%= link_to title_for_node(node), node_path(node) %>
32 <h4><%= link_to title_for_node(node), node_path(node) %></h4> 50 <span class="field_hint"><%= link_to_path("#{node.unique_name} ↗", node.unique_name) %></span>
33 <p><%= link_to_path(node.unique_name, node.unique_name) %></p> 51 </div>
34 </td> 52 <span class="dashboard_widget_meta">
35 <td class="actions"> 53 <%= (editor = node_head_editor(node)) ? t("published_by", editor: editor) : t("published") %>, <%= relative_time_phrase(node.head.updated_at) %>
36 <%= link_to 'show', node_path(node) %> 54 </span>
37 <%= link_to 'Revisions', node_revisions_path(node) %> 55 </li>
38 </td>
39 <td>
40 <%= node.lock_owner.login if node.lock_owner %>
41 </td>
42 <td>
43 <%= link_to ( node.draft ? node.draft.revision : (node.head ? node.head.revision : "EMPTY" ) ), node_revisions_path(node) %>
44 </td>
45 </tr>
46 <% end %> 56 <% end %>
47 </table> 57 </ul>
58 <%= link_to "See all recent changes →", recent_nodes_path %>
59 </div>
48</div> 60</div>
49 61
50<div id="my_work_table"> 62<div id="dashboard_housekeeping">
51 63 <h3>Housekeeping</h3>
52 <h2>My Work</h2> 64 <div class="button_row">
53 65 <%= link_to nodes_path, class: "action_button" do %>
54 <table class="node_table"> 66 <%= icon("list", library: "tabler", "aria-hidden": true) %> Nodes
55 <tr class="header">
56 <th class="node_id">ID</th>
57 <th class="title">Title</th>
58 <th class="actions">Actions</th>
59 <th class="editor">Locked by</th>
60 <th class="revision">Rev.</th>
61 </tr>
62 <% @mynodes.each do |node| %>
63 <tr class="<%= cycle("even", "odd") %>">
64 <td class="node_id"><%= node.id %></td>
65 <td class="title">
66 <h4><%= link_to title_for_node(node), node_path(node) %></h4>
67 <p><%= link_to_path(node.unique_name, node.unique_name) %></p>
68 </td>
69 <td class="actions">
70 <%= link_to 'show', node_path(node) %>
71 <%= link_to 'Revisions', node_revisions_path(node) %>
72 </td>
73 <td>
74 <%= node.lock_owner.login if node.lock_owner %>
75 </td>
76 <td>
77 <%= link_to ( node.draft ? node.draft.revision : (node.head ? node.head.revision : "EMPTY" ) ), node_revisions_path(node) %>
78 </td>
79 </tr>
80 <% end %> 67 <% end %>
81 </table> 68 <%= link_to events_path, class: "action_button" do %>
82</div> 69 <%= icon("calendar", library: "tabler", "aria-hidden": true) %> Events
83
84<div id="current_drafts_table">
85
86 <h2>Current Drafts & Autosaves (<%= @drafts_count %>)</h2>
87
88 <table class="node_table">
89 <tr class="header">
90 <th class="node_id">ID</th>
91 <th class="title">Title</th>
92 <th class="actions">Actions</th>
93 <th class="editor">Locked by</th>
94 <th class="revision">Rev.</th>
95 <th class="autosave">Autosave</th>
96 </tr>
97 <% @drafts.each do |node| %>
98 <tr class="<%= cycle("even", "odd") %>">
99 <td class="node_id"><%= node.id %></td>
100 <td class="title">
101 <h4><%= link_to title_for_node(node), node_path(node) %></h4>
102 <p><%= link_to_path(node.unique_name, node.unique_name) %></p>
103 </td>
104 <td class="actions">
105 <%= link_to 'show', node_path(node) %>
106 <%= link_to 'Revisions', node_revisions_path(node) %>
107 <% if pair = node.available_layer_pairs.last %>
108 <%= link_to 'diff', diff_node_revisions_path(node, start_revision: pair.first, end_revision: pair.last) %>
109 <% end %>
110 </td>
111 <td>
112 <%= node.lock_owner.login if node.lock_owner %>
113 </td>
114 <td>
115 <%= link_to ( node.draft ? node.draft.revision : (node.head ? node.head.revision : "EMPTY" ) ), node_revisions_path(node) %>
116 </td>
117 <td class="autosave">
118 <%= node.autosave ? "unsaved changes" : "" %>
119 </td>
120 </tr>
121 <% end %> 70 <% end %>
122 </table> 71 <%= link_to assets_path, class: "action_button" do %>
123</div> 72 <%= icon("folder", library: "tabler", "aria-hidden": true) %> Assets
124 73 <% end %>
125<div id="admin_sitemap_table"> 74 <%= link_to users_path, class: "action_button" do %>
126 75 <%= icon("users", library: "tabler", "aria-hidden": true) %> Users
127 <h2>Sitemap</h2>
128
129 <table class="node_table">
130 <tr class="header">
131 <th class="node_id">ID</th>
132 <th class="title">Title</th>
133 <th class="actions">Actions</th>
134 <th class="editor">Locked by</th>
135 </tr>
136 <% @sitemap.each do |node| %>
137 <% if !node.nil? %>
138 <tr class="<%= cycle("even", "odd") %>">
139 <td class="node_id" style="padding-left: <%= @sitemap_depth[node.id] * 30 %>px;"><%= node.id %></td>
140 <td class="title" style="padding-left: <%= @sitemap_depth[node.id] * 30 %>px;">
141 <h4><%= link_to title_for_node(node), node_path(node) %></h4>
142 <p><%= link_to_path(node.unique_name, node.unique_name) %></p>
143 </td>
144 <td class="actions">
145 <%= link_to 'create subpage', new_node_path(:parent_id => node.id) %>
146 <%= link_to 'Revisions', node_revisions_path(node) %>
147 </td>
148 <td>
149 <%= node.lock_owner.login if node.lock_owner %>
150 </td>
151 </tr>
152 <% end %> 76 <% end %>
77 <%= link_to menu_items_path, class: "action_button" do %>
78 <%= icon("menu-2", library: "tabler", "aria-hidden": true) %> Navigation
153 <% end %> 79 <% end %>
154 </table> 80 </div>
155</div> 81</div>