1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.console.widgets;
20
21 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
22 import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
23 import java.io.Serializable;
24 import java.time.Duration;
25 import java.time.temporal.ChronoUnit;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Optional;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.syncope.client.console.SyncopeConsoleSession;
33 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
34 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
35 import org.apache.syncope.client.console.pages.BasePage;
36 import org.apache.syncope.client.console.panels.DirectoryPanel;
37 import org.apache.syncope.client.console.panels.ExecMessageModal;
38 import org.apache.syncope.client.console.reports.ReportWizardBuilder;
39 import org.apache.syncope.client.console.rest.BaseRestClient;
40 import org.apache.syncope.client.console.rest.ImplementationRestClient;
41 import org.apache.syncope.client.console.rest.NotificationRestClient;
42 import org.apache.syncope.client.console.rest.RealmRestClient;
43 import org.apache.syncope.client.console.rest.ReportRestClient;
44 import org.apache.syncope.client.console.rest.TaskRestClient;
45 import org.apache.syncope.client.console.tasks.SchedTaskWizardBuilder;
46 import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
47 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
48 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
49 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
50 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
51 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
52 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
53 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
54 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
55 import org.apache.syncope.client.ui.commons.Constants;
56 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
57 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
58 import org.apache.syncope.common.lib.SyncopeClientException;
59 import org.apache.syncope.common.lib.to.ExecTO;
60 import org.apache.syncope.common.lib.to.JobTO;
61 import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
62 import org.apache.syncope.common.lib.to.PullTaskTO;
63 import org.apache.syncope.common.lib.to.ReportTO;
64 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
65 import org.apache.syncope.common.lib.types.JobAction;
66 import org.apache.syncope.common.lib.types.JobType;
67 import org.apache.syncope.common.lib.types.TaskType;
68 import org.apache.wicket.PageReference;
69 import org.apache.wicket.ajax.AjaxRequestTarget;
70 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
71 import org.apache.wicket.event.IEvent;
72 import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
73 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
74 import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
75 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
76 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
77 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
78 import org.apache.wicket.extensions.markup.html.tabs.ITab;
79 import org.apache.wicket.markup.html.WebMarkupContainer;
80 import org.apache.wicket.markup.html.WebPage;
81 import org.apache.wicket.markup.html.panel.Panel;
82 import org.apache.wicket.markup.repeater.Item;
83 import org.apache.wicket.model.CompoundPropertyModel;
84 import org.apache.wicket.model.IModel;
85 import org.apache.wicket.model.Model;
86 import org.apache.wicket.model.ResourceModel;
87 import org.apache.wicket.model.StringResourceModel;
88 import org.apache.wicket.spring.injection.annot.SpringBean;
89
90 public class JobWidget extends BaseWidget {
91
92 private static final long serialVersionUID = 7667120094526529934L;
93
94 protected static final int ROWS = 5;
95
96 @SpringBean
97 protected NotificationRestClient notificationRestClient;
98
99 @SpringBean
100 protected ReportRestClient reportRestClient;
101
102 @SpringBean
103 protected TaskRestClient taskRestClient;
104
105 @SpringBean
106 protected RealmRestClient realmRestClient;
107
108 @SpringBean
109 protected ImplementationRestClient implementationRestClient;
110
111 @SpringBean
112 protected MIMETypesLoader mimeTypesLoader;
113
114 protected final ActionLinksTogglePanel<JobTO> actionTogglePanel;
115
116 protected final BaseModal<Serializable> modal = new BaseModal<>("modal") {
117
118 private static final long serialVersionUID = 389935548143327858L;
119
120 @Override
121 protected void onConfigure() {
122 super.onConfigure();
123 setFooterVisible(false);
124 }
125 };
126
127 protected final BaseModal<Serializable> detailModal = new BaseModal<>("detailModal") {
128
129 private static final long serialVersionUID = 389935548143327858L;
130
131 @Override
132 protected void onConfigure() {
133 super.onConfigure();
134 setFooterVisible(true);
135 }
136 };
137
138 protected final BaseModal<ReportTO> reportModal = new BaseModal<>("reportModal") {
139
140 private static final long serialVersionUID = 389935548143327858L;
141
142 @Override
143 protected void onConfigure() {
144 super.onConfigure();
145 setFooterVisible(false);
146 }
147 };
148
149 protected final WebMarkupContainer container;
150
151 protected final List<JobTO> available;
152
153 protected AvailableJobsPanel availableJobsPanel;
154
155 protected final List<ExecTO> recent;
156
157 protected RecentExecPanel recentExecPanel;
158
159 public JobWidget(final String id, final PageReference pageRef) {
160 super(id);
161 setOutputMarkupId(true);
162 add(modal);
163 modal.setWindowClosedCallback(target -> modal.show(false));
164
165 add(detailModal);
166 detailModal.setWindowClosedCallback(target -> detailModal.show(false));
167
168 add(reportModal);
169 reportModal.setWindowClosedCallback(target -> reportModal.show(false));
170
171 reportModal.size(Modal.Size.Large);
172
173 available = getUpdatedAvailable();
174 recent = getUpdatedRecent();
175
176 container = new WebMarkupContainer("jobContainer");
177 container.add(new IndicatorAjaxTimerBehavior(Duration.of(10, ChronoUnit.SECONDS)) {
178
179 private static final long serialVersionUID = 7298597675929755960L;
180
181 @Override
182 protected void onTimer(final AjaxRequestTarget target) {
183 List<JobTO> updatedAvailable = getUpdatedAvailable();
184 if (!updatedAvailable.equals(available)) {
185 available.clear();
186 available.addAll(updatedAvailable);
187 if (availableJobsPanel != null) {
188 availableJobsPanel.modelChanged();
189 target.add(availableJobsPanel);
190 }
191 }
192
193 List<ExecTO> updatedRecent = getUpdatedRecent();
194 if (!updatedRecent.equals(recent)) {
195 recent.clear();
196 recent.addAll(updatedRecent);
197 if (recentExecPanel != null) {
198 recentExecPanel.modelChanged();
199 target.add(recentExecPanel);
200 }
201 }
202 }
203 });
204 add(container);
205
206 container.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList(pageRef)));
207
208 actionTogglePanel = new ActionLinksTogglePanel<>("actionTogglePanel", pageRef);
209 add(actionTogglePanel);
210 }
211
212 protected List<JobTO> getUpdatedAvailable() {
213 List<JobTO> updatedAvailable = new ArrayList<>();
214
215 if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.NOTIFICATION_LIST)) {
216 JobTO notificationJob = notificationRestClient.getJob();
217 if (notificationJob != null) {
218 updatedAvailable.add(notificationJob);
219 }
220 }
221 if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.TASK_LIST)) {
222 updatedAvailable.addAll(taskRestClient.listJobs());
223 }
224 if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.REPORT_LIST)) {
225 updatedAvailable.addAll(reportRestClient.listJobs());
226 }
227
228 return updatedAvailable;
229 }
230
231 protected List<ExecTO> getUpdatedRecent() {
232 List<ExecTO> updatedRecent = new ArrayList<>();
233
234 if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.TASK_LIST)) {
235 updatedRecent.addAll(taskRestClient.listRecentExecutions(10));
236 }
237 if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.REPORT_LIST)) {
238 updatedRecent.addAll(reportRestClient.listRecentExecutions(10));
239 }
240
241 return updatedRecent;
242 }
243
244 protected List<ITab> buildTabList(final PageReference pageRef) {
245 List<ITab> tabs = new ArrayList<>();
246
247 tabs.add(new AbstractTab(new ResourceModel("available")) {
248
249 private static final long serialVersionUID = -6815067322125799251L;
250
251 @Override
252 public Panel getPanel(final String panelId) {
253 availableJobsPanel = new AvailableJobsPanel(panelId, pageRef);
254 availableJobsPanel.setOutputMarkupId(true);
255 return availableJobsPanel;
256 }
257 });
258
259 tabs.add(new AbstractTab(new ResourceModel("recent")) {
260
261 private static final long serialVersionUID = -6815067322125799251L;
262
263 @Override
264 public Panel getPanel(final String panelId) {
265 recentExecPanel = new RecentExecPanel(panelId, pageRef);
266 recentExecPanel.setOutputMarkupId(true);
267 return recentExecPanel;
268 }
269 });
270
271 return tabs;
272 }
273
274 @Override
275 public void onEvent(final IEvent<?> event) {
276 if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
277 available.clear();
278 available.addAll(getUpdatedAvailable());
279 availableJobsPanel.modelChanged();
280 JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(availableJobsPanel);
281 }
282 }
283
284 protected class AvailableJobsPanel extends DirectoryPanel<JobTO, JobTO, AvailableJobsProvider, BaseRestClient> {
285
286 private static final long serialVersionUID = -8214546246301342868L;
287
288 private final BaseModal<ReportTO> reportModal;
289
290 private final BaseModal<Serializable> jobModal;
291
292 AvailableJobsPanel(final String id, final PageReference pageRef) {
293 super(id, new Builder<JobTO, JobTO, BaseRestClient>(null, pageRef) {
294
295 private static final long serialVersionUID = 8769126634538601689L;
296
297 @Override
298 protected WizardMgtPanel<JobTO> newInstance(final String id, final boolean wizardInModal) {
299 throw new UnsupportedOperationException();
300 }
301 }.disableCheckBoxes().hidePaginator());
302
303 this.reportModal = JobWidget.this.reportModal;
304 setWindowClosedReloadCallback(reportModal);
305
306 this.jobModal = JobWidget.this.modal;
307 setWindowClosedReloadCallback(jobModal);
308
309 rows = ROWS;
310 initResultTable();
311 }
312
313 @Override
314 protected AvailableJobsProvider dataProvider() {
315 return new AvailableJobsProvider();
316 }
317
318 @Override
319 protected String paginatorRowsKey() {
320 return StringUtils.EMPTY;
321 }
322
323 @Override
324 protected Collection<ActionLink.ActionType> getBatches() {
325 return List.of();
326 }
327
328 @Override
329 protected List<IColumn<JobTO, String>> getColumns() {
330 List<IColumn<JobTO, String>> columns = new ArrayList<>();
331
332 columns.add(new PropertyColumn<>(new ResourceModel("refDesc"), "refDesc", "refDesc"));
333
334 columns.add(new BooleanPropertyColumn<>(new ResourceModel("scheduled"), "scheduled", "scheduled"));
335
336 columns.add(new DatePropertyColumn<>(new ResourceModel("start"), "start", "start"));
337
338 columns.add(new AbstractColumn<>(new Model<>(""), "running") {
339
340 private static final long serialVersionUID = -4008579357070833846L;
341
342 @Override
343 public void populateItem(
344 final Item<ICellPopulator<JobTO>> cellItem,
345 final String componentId,
346 final IModel<JobTO> rowModel) {
347
348 JobTO jobTO = rowModel.getObject();
349 JobActionPanel panel = new JobActionPanel(componentId, jobTO, true, JobWidget.this);
350
351 String roles;
352 switch (jobTO.getType()) {
353 case TASK:
354 roles = String.format("%s,%s",
355 IdRepoEntitlement.TASK_EXECUTE, IdRepoEntitlement.TASK_UPDATE);
356 break;
357
358 case REPORT:
359 roles = String.format("%s,%s",
360 IdRepoEntitlement.REPORT_EXECUTE, IdRepoEntitlement.REPORT_UPDATE);
361 break;
362
363 case NOTIFICATION:
364 roles = String.format("%s,%s",
365 IdRepoEntitlement.NOTIFICATION_EXECUTE, IdRepoEntitlement.NOTIFICATION_UPDATE);
366 break;
367
368 default:
369 roles = "NO_ROLES";
370 }
371 MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE, roles);
372
373 cellItem.add(panel);
374 }
375
376 @Override
377 public String getCssClass() {
378 return "running-col";
379 }
380 });
381
382 return columns;
383 }
384
385 @Override
386 protected ActionsPanel<JobTO> getActions(final IModel<JobTO> model) {
387 final ActionsPanel<JobTO> panel = super.getActions(model);
388
389 final JobTO jobTO = model.getObject();
390
391 panel.add(new ActionLink<>() {
392
393 private static final long serialVersionUID = -7978723352517770644L;
394
395 @Override
396 public void onClick(final AjaxRequestTarget target, final JobTO ignore) {
397 switch (jobTO.getType()) {
398 case NOTIFICATION:
399 break;
400
401 case REPORT:
402 ReportTO reportTO = reportRestClient.read(jobTO.getRefKey());
403
404 ReportWizardBuilder rwb = new ReportWizardBuilder(
405 reportTO,
406 implementationRestClient,
407 reportRestClient,
408 mimeTypesLoader,
409 pageRef);
410 rwb.setEventSink(AvailableJobsPanel.this);
411
412 target.add(jobModal.setContent(rwb.build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)));
413
414 jobModal.header(new StringResourceModel(
415 "any.edit",
416 AvailableJobsPanel.this,
417 new Model<>(reportTO)));
418
419 jobModal.show(true);
420 break;
421
422 case TASK:
423 ProvisioningTaskTO schedTaskTO;
424 try {
425 schedTaskTO = taskRestClient.readTask(TaskType.PULL, jobTO.getRefKey());
426 } catch (Exception e) {
427 LOG.debug("Failed to read {} as {}, attempting {}",
428 jobTO.getRefKey(), TaskType.PULL, TaskType.PUSH, e);
429 schedTaskTO = taskRestClient.readTask(TaskType.PUSH, jobTO.getRefKey());
430 }
431
432 SchedTaskWizardBuilder<ProvisioningTaskTO> swb =
433 new SchedTaskWizardBuilder<>(schedTaskTO instanceof PullTaskTO
434 ? TaskType.PULL : TaskType.PUSH, schedTaskTO,
435 realmRestClient, taskRestClient, pageRef);
436 swb.setEventSink(AvailableJobsPanel.this);
437
438 target.add(jobModal.setContent(swb.build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)));
439
440 jobModal.header(new StringResourceModel(
441 "any.edit",
442 AvailableJobsPanel.this,
443 new Model<>(schedTaskTO)));
444
445 jobModal.show(true);
446 break;
447
448 default:
449 break;
450 }
451 }
452
453 @Override
454 protected boolean statusCondition(final JobTO modelObject) {
455 return !(null != jobTO.getType() && JobType.NOTIFICATION.equals(jobTO.getType()));
456 }
457 }, ActionType.EDIT, IdRepoEntitlement.TASK_UPDATE);
458
459 panel.add(new ActionLink<>() {
460
461 private static final long serialVersionUID = -3722207913631435501L;
462
463 @Override
464 public void onClick(final AjaxRequestTarget target, final JobTO ignore) {
465 try {
466 if (null != jobTO.getType()) {
467 switch (jobTO.getType()) {
468
469 case NOTIFICATION:
470 break;
471
472 case REPORT:
473 reportRestClient.actionJob(jobTO.getRefKey(), JobAction.DELETE);
474 break;
475
476 case TASK:
477 taskRestClient.actionJob(jobTO.getRefKey(), JobAction.DELETE);
478 break;
479
480 default:
481 break;
482 }
483 SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
484 target.add(container);
485 }
486 } catch (SyncopeClientException e) {
487 LOG.error("While deleting object {}", jobTO.getRefKey(), e);
488 SyncopeConsoleSession.get().onException(e);
489 }
490 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
491 }
492
493 @Override
494 protected boolean statusCondition(final JobTO modelObject) {
495 return (null != jobTO.getType()
496 && !JobType.NOTIFICATION.equals(jobTO.getType())
497 && (jobTO.isScheduled() && !jobTO.isRunning()));
498 }
499 }, ActionLink.ActionType.DELETE, IdRepoEntitlement.TASK_DELETE, true);
500
501 return panel;
502 }
503
504 @Override
505 @SuppressWarnings("unchecked")
506 public void onEvent(final IEvent<?> event) {
507 if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent
508 || event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
509
510 Optional<AjaxRequestTarget> target = ((AjaxWizard.NewItemEvent<?>) event.getPayload()).getTarget();
511 target.ifPresent(jobModal::close);
512 }
513
514 super.onEvent(event);
515 }
516 }
517
518 protected final class AvailableJobsProvider extends DirectoryDataProvider<JobTO> {
519
520 private static final long serialVersionUID = 3191573490219472572L;
521
522 private final SortableDataProviderComparator<JobTO> comparator;
523
524 private AvailableJobsProvider() {
525 super(ROWS);
526 setSort("type", SortOrder.ASCENDING);
527 comparator = new SortableDataProviderComparator<>(this);
528 }
529
530 @Override
531 public Iterator<JobTO> iterator(final long first, final long count) {
532 available.sort(comparator);
533 return available.subList((int) first, (int) first + (int) count).iterator();
534 }
535
536 @Override
537 public long size() {
538 return available.size();
539 }
540
541 @Override
542 public IModel<JobTO> model(final JobTO object) {
543 return new CompoundPropertyModel<>(object);
544 }
545 }
546
547 private class RecentExecPanel
548 extends DirectoryPanel<ExecTO, ExecTO, RecentExecPanel.RecentExecProvider, BaseRestClient> {
549
550 private static final long serialVersionUID = -8214546246301342868L;
551
552 RecentExecPanel(final String id, final PageReference pageRef) {
553 super(id, new Builder<ExecTO, ExecTO, BaseRestClient>(null, pageRef) {
554
555 private static final long serialVersionUID = 8769126634538601689L;
556
557 @Override
558 protected WizardMgtPanel<ExecTO> newInstance(final String id, final boolean wizardInModal) {
559 throw new UnsupportedOperationException();
560 }
561 }.disableCheckBoxes().hidePaginator());
562
563 rows = ROWS;
564 initResultTable();
565 }
566
567 @Override
568 protected RecentExecProvider dataProvider() {
569 return new RecentExecProvider();
570 }
571
572 @Override
573 protected String paginatorRowsKey() {
574 return StringUtils.EMPTY;
575 }
576
577 @Override
578 protected Collection<ActionLink.ActionType> getBatches() {
579 return List.of();
580 }
581
582 @Override
583 protected List<IColumn<ExecTO, String>> getColumns() {
584 List<IColumn<ExecTO, String>> columns = new ArrayList<>();
585
586 columns.add(new PropertyColumn<>(new ResourceModel("refDesc"), "refDesc", "refDesc"));
587
588 columns.add(new DatePropertyColumn<>(new ResourceModel("start"), "start", "start"));
589
590 columns.add(new DatePropertyColumn<>(new ResourceModel("end"), "end", "end"));
591
592 columns.add(new PropertyColumn<>(new ResourceModel("status"), "status", "status"));
593
594 return columns;
595 }
596
597 @Override
598 public ActionsPanel<ExecTO> getActions(final IModel<ExecTO> model) {
599 ActionsPanel<ExecTO> panel = super.getActions(model);
600
601 panel.add(new ActionLink<>() {
602
603 private static final long serialVersionUID = -3722207913631435501L;
604
605 @Override
606 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
607 detailModal.header(new StringResourceModel("execution.view", JobWidget.this, model));
608 detailModal.setContent(new ExecMessageModal(model.getObject().getMessage()));
609 detailModal.show(true);
610 target.add(detailModal);
611 }
612 }, ActionLink.ActionType.VIEW, IdRepoEntitlement.TASK_READ);
613
614 return panel;
615 }
616
617 protected final class RecentExecProvider extends DirectoryDataProvider<ExecTO> {
618
619 private static final long serialVersionUID = 2835707012690698633L;
620
621 private final SortableDataProviderComparator<ExecTO> comparator;
622
623 private RecentExecProvider() {
624 super(ROWS);
625 setSort("end", SortOrder.DESCENDING);
626 comparator = new SortableDataProviderComparator<>(this);
627 }
628
629 @Override
630 public Iterator<ExecTO> iterator(final long first, final long count) {
631 recent.sort(comparator);
632 return recent.subList((int) first, (int) first + (int) count).iterator();
633 }
634
635 @Override
636 public long size() {
637 return recent.size();
638 }
639
640 @Override
641 public IModel<ExecTO> model(final ExecTO object) {
642 return new CompoundPropertyModel<>(object);
643 }
644 }
645 }
646 }