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.ReportTO;
63 import org.apache.syncope.common.lib.to.TaskTO;
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 TaskType taskType = null;
424 if (jobTO.getRefDesc().startsWith("SCHEDULED")) {
425 taskType = TaskType.SCHEDULED;
426 } else if (jobTO.getRefDesc().startsWith("PULL")) {
427 taskType = TaskType.PULL;
428 } else if (jobTO.getRefDesc().startsWith("PUSH")) {
429 taskType = TaskType.PUSH;
430 } else if (jobTO.getRefDesc().startsWith("MACRO")) {
431 taskType = TaskType.MACRO;
432 }
433 if (taskType == null) {
434 break;
435 }
436
437 TaskTO taskTO = null;
438 try {
439 taskTO = taskRestClient.readTask(taskType, jobTO.getRefKey());
440 } catch (Exception e) {
441 LOG.debug("Failed to read {} as {}", jobTO.getRefKey(), taskType, e);
442 }
443 if (taskTO == null) {
444 break;
445 }
446
447 if (taskTO instanceof ProvisioningTaskTO) {
448 SchedTaskWizardBuilder<ProvisioningTaskTO> swb =
449 new SchedTaskWizardBuilder<>(taskType, (ProvisioningTaskTO) taskTO,
450 realmRestClient, taskRestClient, pageRef);
451 swb.setEventSink(AvailableJobsPanel.this);
452
453 target.add(jobModal.setContent(swb.build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT)));
454
455 jobModal.header(new StringResourceModel(
456 "any.edit",
457 AvailableJobsPanel.this,
458 new Model<>(taskTO)));
459
460 jobModal.show(true);
461 } else {
462 SyncopeConsoleSession.get().info("Unsupported task type: " + taskType.name());
463 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
464 }
465 break;
466
467 default:
468 break;
469 }
470 }
471
472 @Override
473 protected boolean statusCondition(final JobTO modelObject) {
474 return !(null != jobTO.getType() && JobType.NOTIFICATION.equals(jobTO.getType()));
475 }
476 }, ActionType.EDIT, IdRepoEntitlement.TASK_UPDATE);
477
478 panel.add(new ActionLink<>() {
479
480 private static final long serialVersionUID = -3722207913631435501L;
481
482 @Override
483 public void onClick(final AjaxRequestTarget target, final JobTO ignore) {
484 try {
485 if (null != jobTO.getType()) {
486 switch (jobTO.getType()) {
487
488 case NOTIFICATION:
489 break;
490
491 case REPORT:
492 reportRestClient.actionJob(jobTO.getRefKey(), JobAction.DELETE);
493 break;
494
495 case TASK:
496 taskRestClient.actionJob(jobTO.getRefKey(), JobAction.DELETE);
497 break;
498
499 default:
500 break;
501 }
502 SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
503 target.add(container);
504 }
505 } catch (SyncopeClientException e) {
506 LOG.error("While deleting object {}", jobTO.getRefKey(), e);
507 SyncopeConsoleSession.get().onException(e);
508 }
509 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
510 }
511
512 @Override
513 protected boolean statusCondition(final JobTO modelObject) {
514 return (null != jobTO.getType()
515 && !JobType.NOTIFICATION.equals(jobTO.getType())
516 && (jobTO.isScheduled() && !jobTO.isRunning()));
517 }
518 }, ActionLink.ActionType.DELETE, IdRepoEntitlement.TASK_DELETE, true);
519
520 return panel;
521 }
522
523 @Override
524 @SuppressWarnings("unchecked")
525 public void onEvent(final IEvent<?> event) {
526 if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent
527 || event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
528
529 Optional<AjaxRequestTarget> target = ((AjaxWizard.NewItemEvent<?>) event.getPayload()).getTarget();
530 target.ifPresent(jobModal::close);
531 }
532
533 super.onEvent(event);
534 }
535 }
536
537 protected final class AvailableJobsProvider extends DirectoryDataProvider<JobTO> {
538
539 private static final long serialVersionUID = 3191573490219472572L;
540
541 private final SortableDataProviderComparator<JobTO> comparator;
542
543 private AvailableJobsProvider() {
544 super(ROWS);
545 setSort("type", SortOrder.ASCENDING);
546 comparator = new SortableDataProviderComparator<>(this);
547 }
548
549 @Override
550 public Iterator<JobTO> iterator(final long first, final long count) {
551 available.sort(comparator);
552 return available.subList((int) first, (int) first + (int) count).iterator();
553 }
554
555 @Override
556 public long size() {
557 return available.size();
558 }
559
560 @Override
561 public IModel<JobTO> model(final JobTO object) {
562 return new CompoundPropertyModel<>(object);
563 }
564 }
565
566 private class RecentExecPanel
567 extends DirectoryPanel<ExecTO, ExecTO, RecentExecPanel.RecentExecProvider, BaseRestClient> {
568
569 private static final long serialVersionUID = -8214546246301342868L;
570
571 RecentExecPanel(final String id, final PageReference pageRef) {
572 super(id, new Builder<ExecTO, ExecTO, BaseRestClient>(null, pageRef) {
573
574 private static final long serialVersionUID = 8769126634538601689L;
575
576 @Override
577 protected WizardMgtPanel<ExecTO> newInstance(final String id, final boolean wizardInModal) {
578 throw new UnsupportedOperationException();
579 }
580 }.disableCheckBoxes().hidePaginator());
581
582 rows = ROWS;
583 initResultTable();
584 }
585
586 @Override
587 protected RecentExecProvider dataProvider() {
588 return new RecentExecProvider();
589 }
590
591 @Override
592 protected String paginatorRowsKey() {
593 return StringUtils.EMPTY;
594 }
595
596 @Override
597 protected Collection<ActionLink.ActionType> getBatches() {
598 return List.of();
599 }
600
601 @Override
602 protected List<IColumn<ExecTO, String>> getColumns() {
603 List<IColumn<ExecTO, String>> columns = new ArrayList<>();
604
605 columns.add(new PropertyColumn<>(new ResourceModel("refDesc"), "refDesc", "refDesc"));
606
607 columns.add(new DatePropertyColumn<>(new ResourceModel("start"), "start", "start"));
608
609 columns.add(new DatePropertyColumn<>(new ResourceModel("end"), "end", "end"));
610
611 columns.add(new PropertyColumn<>(new ResourceModel("status"), "status", "status"));
612
613 return columns;
614 }
615
616 @Override
617 public ActionsPanel<ExecTO> getActions(final IModel<ExecTO> model) {
618 ActionsPanel<ExecTO> panel = super.getActions(model);
619
620 panel.add(new ActionLink<>() {
621
622 private static final long serialVersionUID = -3722207913631435501L;
623
624 @Override
625 public void onClick(final AjaxRequestTarget target, final ExecTO ignore) {
626 detailModal.header(new StringResourceModel("execution.view", JobWidget.this, model));
627 detailModal.setContent(new ExecMessageModal(model.getObject().getMessage()));
628 detailModal.show(true);
629 target.add(detailModal);
630 }
631 }, ActionLink.ActionType.VIEW, IdRepoEntitlement.TASK_READ);
632
633 return panel;
634 }
635
636 protected final class RecentExecProvider extends DirectoryDataProvider<ExecTO> {
637
638 private static final long serialVersionUID = 2835707012690698633L;
639
640 private final SortableDataProviderComparator<ExecTO> comparator;
641
642 private RecentExecProvider() {
643 super(ROWS);
644 setSort("end", SortOrder.DESCENDING);
645 comparator = new SortableDataProviderComparator<>(this);
646 }
647
648 @Override
649 public Iterator<ExecTO> iterator(final long first, final long count) {
650 recent.sort(comparator);
651 return recent.subList((int) first, (int) first + (int) count).iterator();
652 }
653
654 @Override
655 public long size() {
656 return recent.size();
657 }
658
659 @Override
660 public IModel<ExecTO> model(final ExecTO object) {
661 return new CompoundPropertyModel<>(object);
662 }
663 }
664 }
665 }