1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.admincentral;
35
36 import static com.vaadin.server.Sizeable.Unit.*;
37 import static com.vaadin.ui.Alignment.TOP_LEFT;
38
39 import info.magnolia.admincentral.banner.BannerContainer;
40 import info.magnolia.admincentral.components.InstanceInfo;
41 import info.magnolia.admincentral.findbar.FindBar;
42 import info.magnolia.admincentral.usermenu.UserMenu;
43 import info.magnolia.admincentral.usermenu.actions.UserActionExecutor;
44 import info.magnolia.cms.security.User;
45 import info.magnolia.context.Context;
46 import info.magnolia.event.EventBus;
47 import info.magnolia.i18nsystem.SimpleTranslator;
48 import info.magnolia.init.MagnoliaConfigurationProperties;
49 import info.magnolia.objectfactory.ComponentProvider;
50 import info.magnolia.objectfactory.Components;
51 import info.magnolia.task.Task;
52 import info.magnolia.task.TasksManager;
53 import info.magnolia.ui.api.app.launcherlayout.AppLauncherLayout;
54 import info.magnolia.ui.api.app.launcherlayout.AppLauncherLayoutManager;
55 import info.magnolia.ui.api.event.AdmincentralEventBus;
56 import info.magnolia.ui.api.location.DefaultLocation;
57 import info.magnolia.ui.api.location.LocationChangedEvent;
58 import info.magnolia.ui.api.location.LocationController;
59 import info.magnolia.ui.api.message.Message;
60 import info.magnolia.ui.api.message.MessageType;
61 import info.magnolia.ui.form.field.converter.NonRoundingConverterFactory;
62 import info.magnolia.ui.framework.ioc.CurrentUiContextReference;
63 import info.magnolia.ui.framework.ioc.SessionStore;
64 import info.magnolia.ui.framework.ioc.UiContextBoundComponentProvider;
65 import info.magnolia.ui.framework.ioc.UiContextReference;
66 import info.magnolia.ui.framework.message.LocalMessageDispatcher;
67 import info.magnolia.ui.framework.message.MessagesManager;
68
69 import java.util.Arrays;
70 import java.util.HashMap;
71 import java.util.Map;
72
73 import org.apache.commons.lang3.StringUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 import com.google.inject.Key;
78 import com.google.inject.name.Names;
79 import com.vaadin.annotations.JavaScript;
80 import com.vaadin.annotations.Theme;
81 import com.vaadin.server.Page;
82 import com.vaadin.server.Responsive;
83 import com.vaadin.server.ThemeResource;
84 import com.vaadin.server.VaadinRequest;
85 import com.vaadin.server.VaadinSession;
86 import com.vaadin.shared.ui.ContentMode;
87 import com.vaadin.ui.Component;
88 import com.vaadin.ui.HorizontalLayout;
89 import com.vaadin.ui.Image;
90 import com.vaadin.ui.Label;
91 import com.vaadin.ui.UI;
92 import com.vaadin.ui.VerticalLayout;
93
94
95
96
97
98
99 @Theme("resurface-admincentral")
100 @JavaScript({ "jquery-3.3.1.min.js", "jquery-migrate-3.0.1.min.js" })
101 public class ResurfaceUI extends UI {
102 public static final String MESSAGE_COUNT = "MESSAGE_COUNT";
103 private static final String HEADER_INDICATOR = "%s<span class='status icon-radio_fill color-%s'></span>";
104 private static final String[] HEADER_APPS = { "messages-content-app", "tasks-app" };
105 private static final Logger log = LoggerFactory.getLogger(ResurfaceUI.class);
106
107 private ComponentProvider componentProvider;
108 private LocationController locationController;
109 private AppLauncherLayoutManager appLauncherLayoutManager;
110
111 private EventBus eventBus;
112
113 private MessagesManager messagesManager;
114 private LocalMessageDispatcher messageDispatcher;
115 private User user;
116
117 private SimpleTranslator i18n;
118 private MessagesManager.MessageListener messageIndicatorListener;
119
120 @Override
121 protected void init(VaadinRequest request) {
122 log.debug("Initializing Resurface Admincentral UI...");
123 getPage().setTitle("Admincentral - Magnolia");
124
125
126 VaadinSession currentSession = VaadinSession.getCurrent();
127 currentSession.setConverterFactory(new NonRoundingConverterFactory());
128
129 log.debug("Initializing ComponentProvider with Admincentral UiContext...");
130 UiContextReference admincentralUiReference = UiContextReference.ofUi(this);
131
132 Map<Key, Object> instances = new HashMap<>();
133 instances.put(Key.get(CurrentUiContextReference.class), new CurrentUiContextReference());
134 SessionStore.access().createBeanStore(admincentralUiReference, instances);
135
136 this.componentProvider = new UiContextBoundComponentProvider(admincentralUiReference);
137
138
139 this.locationController = componentProvider.getComponent(LocationController.class);
140 this.appLauncherLayoutManager = componentProvider.getComponent(AppLauncherLayoutManager.class);
141 this.eventBus = Components.getComponentWithAnnotation(EventBus.class, Names.named(AdmincentralEventBus.NAME));
142
143 messageDispatcher = componentProvider.newInstance(LocalMessageDispatcher.class, this);
144 messagesManager = componentProvider.getComponent(MessagesManager.class);
145 user = componentProvider.getComponent(Context.class).getUser();
146 messagesManager.registerMessagesListener(user.getName(), messageDispatcher);
147
148 i18n = componentProvider.getComponent(SimpleTranslator.class);
149
150 UI.getCurrent().setErrorHandler(new AdmincentralErrorHandler(messagesManager, i18n));
151
152
153
154
155
156 handleLocationChange();
157
158 initUI();
159
160 String initialFragment = UI.getCurrent().getPage().getUriFragment();
161 checkAndNavigate(initialFragment);
162 }
163
164 private void initUI() {
165 log.debug("Building Resurface Layout...");
166
167 VerticalLayout layout = new VerticalLayout();
168 layout.setSizeFull();
169 layout.setMargin(true);
170 layout.setSpacing(true);
171 layout.addStyleName("shell");
172
173 HorizontalLayout header = new HorizontalLayout();
174 header.setWidth(100, PERCENTAGE);
175 header.setHeight(75, PIXELS);
176 header.setSpacing(true);
177 header.addStyleName("header");
178
179
180 Image logo = new Image(null, new ThemeResource("img/logo-magnolia.svg"));
181 logo.setAlternateText("Magnolia logo");
182 logo.addStyleName("logo");
183
184
185 ViewportLayout viewportLayout = new ViewportLayout();
186 FindBar findBar = componentProvider.newInstance(FindBar.class, viewportLayout);
187
188
189 Component findBarTextBox = findBar.asVaadinComponent();
190 Component messagesButton = createMessagesButton();
191 Component tasksButton = createTasksButton();
192 Component instanceInfoComponent = new InstanceInfo(componentProvider.getComponent(MagnoliaConfigurationProperties.class)).asVaadinComponent();
193 Component userMenuComponent = new UserMenu(this, componentProvider.newInstance(UserActionExecutor.class), messagesManager, user).asVaadinComponent();
194
195 header.addComponents(logo, findBarTextBox, tasksButton, messagesButton, instanceInfoComponent, userMenuComponent);
196 header.setComponentAlignment(logo, TOP_LEFT);
197 header.setExpandRatio(findBarTextBox, 1f);
198
199 layout.addComponents(header);
200 layout.setExpandRatio(header, 0f);
201 BannerContainer bannerContainer = new BannerContainer(eventBus, locationController, i18n);
202 layout.addComponent(bannerContainer);
203 layout.addComponent(viewportLayout);
204 layout.setExpandRatio(viewportLayout, 1f);
205 Responsive.makeResponsive(layout);
206 setContent(layout);
207 }
208
209 private Component createMessagesButton() {
210 final Runnable messageLayoutListener = () -> locationController.goTo(new DefaultLocation("app", "messages-content-app"));
211
212 final long messagesAmount = messagesManager.getMessagesAmount(user.getName(), Arrays.asList(MessageType.values()));
213 Component messagesButton = createIndicatorComponent("Messages", messagesAmount, getMessageConsolidatedStatus(), messageLayoutListener);
214
215 final Label[] messageCountLabel = new Label[1];
216 ((VerticalLayout) messagesButton).iterator().forEachRemaining(c -> {
217 if (MESSAGE_COUNT.equals(c.getId())) {
218 messageCountLabel[0] = (Label) c;
219 updateMessageCountIndicator(messageCountLabel[0], messagesAmount);
220 }
221 });
222
223 messageIndicatorListener = new MessagesManager.MessageListener() {
224
225 @Override
226 public void messageSent(Message message) {
227 final long messagesAmount = messagesManager.getMessagesAmount(user.getName(), Arrays.asList(MessageType.values()));
228 updateMessageCountIndicator(messageCountLabel[0], messagesAmount);
229 }
230
231 @Override
232 public void messageCleared(Message message) {
233 final long messagesAmount = messagesManager.getMessagesAmount(user.getName(), Arrays.asList(MessageType.values()));
234 updateMessageCountIndicator(messageCountLabel[0], messagesAmount);
235 }
236
237 @Override
238 public void messageRemoved(String id) {
239 final long messagesAmount = messagesManager.getMessagesAmount(user.getName(), Arrays.asList(MessageType.values()));
240 updateMessageCountIndicator(messageCountLabel[0], messagesAmount);
241 }
242 };
243 messagesManager.registerMessagesListener(user.getName(), messageIndicatorListener);
244
245 return messagesButton;
246 }
247
248 private void updateMessageCountIndicator(Label messageCountLabel, long numberMessage) {
249 final String messageConsolidatedStatus = getMessageConsolidatedStatus();
250 String messageCountString = String.valueOf(numberMessage);
251 if (StringUtils.isNotEmpty(messageConsolidatedStatus)) {
252 messageCountString = String.format(HEADER_INDICATOR, numberMessage, messageConsolidatedStatus);
253 }
254 messageCountLabel.setValue(messageCountString);
255 }
256
257 private Component createTasksButton() {
258 TasksManager tasksManager = componentProvider.getComponent(TasksManager.class);
259 final Runnable tasksLayoutListener = () -> locationController.goTo(new DefaultLocation("app", "tasks-app"));
260 String status = "";
261 if (tasksManager.getTasksAmountByUserAndStatus(user.getName(), Arrays.asList(Task.Status.Failed)) > 0) {
262 status = "red";
263 } else if (tasksManager.getTasksAmountByUserAndStatus(user.getName(), Arrays.asList(Task.Status.InProgress)) > 0) {
264 status = "yellow";
265 } else if (tasksManager.getTasksAmountByUserAndStatus(user.getName(), Arrays.asList(Task.Status.Created)) > 0) {
266 status = "green";
267 }
268
269 final long tasksAmount = tasksManager.getTasksAmountByUserAndStatus(user.getName(), Arrays.asList(Task.Status.Created));
270 return createIndicatorComponent(i18n.translate("tasks.app.caption"), tasksAmount, status, tasksLayoutListener);
271 }
272
273 private String getMessageConsolidatedStatus() {
274 final int numberOfUnclearedMessages = messagesManager.getNumberOfUnclearedMessagesForUser(user.getName());
275 return numberOfUnclearedMessages > 0 ? "green" : "";
276 }
277
278 private Component createIndicatorComponent(String caption, long value, String status, Runnable runnable) {
279
280 VerticalLayout layout = new VerticalLayout();
281 layout.setSizeUndefined();
282 layout.setMargin(true);
283 layout.setSpacing(false);
284 layout.addStyleName("header-component");
285
286 Label itemCount = new Label();
287
288 if (StringUtils.isEmpty(status)) {
289 itemCount.setValue(String.valueOf(value));
290 } else {
291 itemCount.setValue(String.format(HEADER_INDICATOR, value, status));
292 }
293
294
295 itemCount.addStyleNames("indicator", "heading-2");
296 itemCount.setContentMode(ContentMode.HTML);
297 itemCount.setId(MESSAGE_COUNT);
298
299 Label label = new Label(caption);
300 label.addStyleName("label");
301
302 layout.addComponents(itemCount, label);
303 layout.addLayoutClickListener(event -> runnable.run());
304
305 return layout;
306 }
307
308 private void handleLocationChange() {
309 Page.getCurrent().addPopStateListener(event -> {
310 String fragment = event.getPage().getUriFragment();
311 checkAndNavigate(fragment);
312 });
313
314 eventBus.addHandler(LocationChangedEvent.class, event -> {
315
316
317 if (StringUtils.isBlank(event.getNewLocation().toString())) {
318 Page.getCurrent().reload();
319 } else {
320 Page.getCurrent().setUriFragment(event.getNewLocation().toString());
321 }
322 });
323 }
324
325 private void checkAndNavigate(String fragment) {
326 DefaultLocation newLocation = fragment != null ? new DefaultLocation(fragment) : new DefaultLocation();
327
328
329 AppLauncherLayout userLayout = appLauncherLayoutManager.getLayoutForUser(user);
330 if (userLayout.containsApp(newLocation.getAppName()) || isHeaderApp(newLocation.getAppName())) {
331 locationController.goTo(newLocation);
332 } else {
333 log.debug("Denying user {} request to open app {} without permissions", user.getName(), newLocation.getAppName());
334 }
335 }
336
337 private boolean isHeaderApp(String appName) {
338
339
340 return Arrays.stream(HEADER_APPS)
341 .anyMatch(headerAppName -> headerAppName.equalsIgnoreCase(appName));
342 }
343
344 @Override
345 public void detach() {
346 try {
347 SessionStore.access().getBeanStore(UiContextReference.ofUi(this)).clear();
348 messagesManager.unregisterMessagesListener(user.getName(), messageIndicatorListener);
349 messagesManager.unregisterMessagesListener(user.getName(), messageDispatcher);
350
351 super.detach();
352 } catch (Exception e) {
353 getErrorHandler().error(new com.vaadin.server.ErrorEvent(e));
354 }
355 }
356 }