tim-connector: Changes in JobSchedulerController and zul

Lots of changes:
* Created a new model to follow the standard structure of controllers in
  LibrePlan.
* Marked several strings to be internationalized.
* Improved .zul page, removing unneeded stuff and simplifying code.
* Checked that cron expression is right, otherwise inform the user.

FEA: ItEr77S16JiraAndTimConnectorContributionIntegration
This commit is contained in:
Manuel Rego Casasnovas 2013-02-12 14:24:35 +01:00
parent 8824c9a24a
commit c5796d602b
5 changed files with 313 additions and 320 deletions

View file

@ -0,0 +1,40 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2013 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.common;
import java.util.List;
import org.libreplan.importers.SchedulerInfo;
/**
* Contract for {@link JobSchedulerModel}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IJobSchedulerModel {
List<SchedulerInfo> getSchedulerInfos();
void doManual(SchedulerInfo schedulerInfo);
void saveJobConfigurationAndReschedule(String jobGroup, String jobName,
String cronExp);
}

View file

@ -19,38 +19,31 @@
package org.libreplan.web.common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;
import static org.libreplan.web.I18nHelper._;
import org.apache.commons.lang.Validate;
import java.text.ParseException;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.importers.ISchedulerManager;
import org.libreplan.importers.SchedulerInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.quartz.CronExpression;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Cell;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Hbox;
import org.zkoss.zul.Label;
import org.zkoss.zul.Popup;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Rows;
import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Toolbarbutton;
import org.zkoss.zul.api.Textbox;
/**
* Controller for job scheduler manager
@ -58,289 +51,148 @@ import org.zkoss.zul.Toolbarbutton;
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JobSchedulerController extends GenericForwardComposer {
private static final Log LOG = LogFactory
.getLog(JobSchedulerController.class);
private Grid jobSchedulerGrid, cronExpressionGrid;
private Grid jobSchedulerGrid;
private Popup cronExpressionInputPopup;
private Label jobGroupLabel, jobNameLable;
private Label jobGroup;
@Autowired
ISchedulerManager schedulerManager;
private Label jobName;
@Autowired
IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO;
private Textbox cronExpressionSeconds;
private Textbox cronExpressionMinutes;
private Textbox cronExpressionHours;
private Textbox cronExpressionDayOfMonth;
private Textbox cronExpressionMonth;
private Textbox cronExpressionDayOfWeek;
private Textbox cronExpressionYear;
private IJobSchedulerModel jobSchedulerModel;
@Override
public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp);
comp.setVariable("jobSchedulerController", this, true);
reloadSchedulerJobs();
comp.setAttribute("jobSchedulerController", this);
}
public List<SchedulerInfo> getSchedulerInfo() {
List<SchedulerInfo> schedulerInfoList = schedulerManager
.getSchedulerInfos();
Collections.sort(schedulerInfoList, new Comparator<SchedulerInfo>() {
public List<SchedulerInfo> getSchedulerInfos() {
return jobSchedulerModel.getSchedulerInfos();
}
public RowRenderer getJobSchedulingRenderer() {
return new RowRenderer() {
@Override
public int compare(SchedulerInfo o1, SchedulerInfo o2) {
int result = o1
.getJobSchedulerConfiguration()
.getJobGroup()
.compareTo(
o2.getJobSchedulerConfiguration().getJobGroup());
if (result == 0) {
result = o1
.getJobSchedulerConfiguration()
.getJobName()
.compareTo(
o2.getJobSchedulerConfiguration()
.getJobName());
}
return result;
public void render(Row row, Object data) {
SchedulerInfo schedulerInfo = (SchedulerInfo) data;
row.setValue(data);
Util.appendLabel(row, schedulerInfo
.getJobSchedulerConfiguration().getJobGroup());
Util.appendLabel(row, schedulerInfo
.getJobSchedulerConfiguration().getJobName());
appendCronExpressionAndButton(row, schedulerInfo);
Util.appendLabel(row, schedulerInfo.getNextFireTime());
appendManualStart(row, schedulerInfo);
}
});
return schedulerInfoList;
}
private void reloadSchedulerJobs() {
jobSchedulerGrid.setModel(new SimpleListModel(getSchedulerInfo()));
jobSchedulerGrid.invalidate();
}
public JobSchedulingRenderer getJobSchedulingRenderer() {
return new JobSchedulingRenderer();
}
public class JobSchedulingRenderer implements RowRenderer {
@Override
public void render(Row row, Object data) {
SchedulerInfo schedulerInfo = (SchedulerInfo) data;
Util.appendLabel(row, schedulerInfo.getJobSchedulerConfiguration()
.getJobGroup());
Util.appendLabel(row, schedulerInfo.getJobSchedulerConfiguration()
.getJobName());
appendCronExpressionAndButton(row, schedulerInfo);
Util.appendLabel(row, schedulerInfo.getNextFireTime());
appendManualStart(row, schedulerInfo);
}
};
}
private void appendCronExpressionAndButton(final Row row,
final SchedulerInfo schedulerInfo) {
final Hbox hBox = new Hbox();
hBox.setWidth("100%");
Cell cell = new Cell();
cell.setHflex("2");
cell.setAlign("left");
Label label = new Label(schedulerInfo.getJobSchedulerConfiguration()
.getCronExpression());
cell.appendChild(label);
Cell cell2 = new Cell();
cell2.setHflex("1");
cell2.setWidth("10px");
final Toolbarbutton button = new Toolbarbutton();
button.setImage("/common/img/ico_editar.png");
cell2.appendChild(button);
hBox.appendChild(cell);
hBox.appendChild(cell2);
button.addEventListener(Events.ON_CLICK, new EventListener() {
hBox.appendChild(label);
Button button = Util.createEditButton(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
setupCronExpressionPopup(schedulerInfo);
cronExpressionInputPopup.open(jobSchedulerGrid, "at_pointer");
cronExpressionInputPopup.open(hBox);
}
});
hBox.appendChild(button);
row.appendChild(hBox);
}
private void setupCronExpressionPopup(final SchedulerInfo schedulerInfo) {
List<CronExpression> list = new ArrayList<CronExpression>();
list.add(getCronExpression(schedulerInfo.getJobSchedulerConfiguration()
.getCronExpression()));
cronExpressionGrid.setModel(new SimpleListModel(list));
JobSchedulerConfiguration jobSchedulerConfiguration = schedulerInfo.getJobSchedulerConfiguration();
jobGroup.setValue(jobSchedulerConfiguration.getJobGroup());
jobName.setValue(jobSchedulerConfiguration.getJobName());
jobGroupLabel = (Label) cronExpressionInputPopup
.getFellowIfAny("jobGroup");
jobNameLable = (Label) cronExpressionInputPopup
.getFellowIfAny("jobName");
jobGroupLabel.setValue(schedulerInfo.getJobSchedulerConfiguration()
.getJobGroup());
jobNameLable.setValue(schedulerInfo.getJobSchedulerConfiguration()
.getJobName());
}
String cronExpression = jobSchedulerConfiguration.getCronExpression();
String[] cronExpressionArray = StringUtils.split(cronExpression);
@Autowired
private IAdHocTransactionService adHocTransactionService;
cronExpressionSeconds.setValue(cronExpressionArray[0]);
cronExpressionMinutes.setValue(cronExpressionArray[1]);
cronExpressionHours.setValue(cronExpressionArray[2]);
cronExpressionDayOfMonth.setValue(cronExpressionArray[3]);
cronExpressionMonth.setValue(cronExpressionArray[4]);
cronExpressionDayOfWeek.setValue(cronExpressionArray[5]);
private void saveJobConfigurationAndReschedule(
final String jobGroup, final String jobName, final String cronExp) {
adHocTransactionService
.runOnAnotherTransaction(new IOnTransaction<Void>() {
@Override
public Void execute() {
JobSchedulerConfiguration jobSchedulerConfiguration = jobSchedulerConfigurationDAO
.findByJobGroupAndJobName(jobGroup, jobName);
jobSchedulerConfiguration.setCronExpression(cronExp);
jobSchedulerConfigurationDAO
.save(jobSchedulerConfiguration);
schedulerManager.rescheduleJob(jobSchedulerConfiguration);
reloadSchedulerJobs();
return null;
}
});
}
private CronExpression getCronExpression(String cronExpressionStr) {
CronExpression cronExpression = new CronExpression();
StringTokenizer st = new StringTokenizer(cronExpressionStr);
int countTokens = st.countTokens();
if (countTokens < 6) {
throw new IllegalArgumentException("Cron expression is not valid");
if (cronExpressionArray.length == 7) {
cronExpressionYear.setValue(cronExpressionArray[6]);
}
cronExpression.setSeconds(getNextToken(st.nextToken()));
cronExpression.setMinutes(getNextToken(st.nextToken()));
cronExpression.setHours(getNextToken(st.nextToken()));
cronExpression.setDayOfMonth(getNextToken(st.nextToken()));
cronExpression.setMonth(getNextToken(st.nextToken()));
cronExpression.setDayOfWeek(getNextToken(st.nextToken()));
if (countTokens > 6) { // optional
cronExpression.setYear(getNextToken(st.nextToken()));
} else {
cronExpression.setYear("");
}
return cronExpression;
}
private String getNextToken(String token) {
return token.isEmpty() ? "" : token.trim();
}
private void appendManualStart(final Row row,
final SchedulerInfo schedulerInfo) {
final Button rescheduleButton = new Button("Manual");
final Button rescheduleButton = new Button(_("Manual"));
rescheduleButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
schedulerManager.doManual(schedulerInfo
.getJobSchedulerConfiguration().getJobName());
jobSchedulerModel.doManual(schedulerInfo);
}
});
row.appendChild(rescheduleButton);
}
public void reschedule() {
jobGroupLabel = (Label) cronExpressionInputPopup
.getFellowIfAny("jobGroup");
jobNameLable = (Label) cronExpressionInputPopup
.getFellowIfAny("jobName");
String cronExpression = getCronExpressionString();
try {
// Check cron expression format
new CronExpression(cronExpression);
} catch (ParseException e) {
LOG.info("Unable to parse cron expression", e);
throw new WrongValueException(cronExpressionInputPopup,
_("Unable to parse cron expression") + ":\n"
+ e.getMessage());
}
Rows rows = cronExpressionGrid.getRows();
Row row = (Row) rows.getChildren().get(0);
CronExpression cronExp = (CronExpression) row.getValue();
saveJobConfigurationAndReschedule(jobGroupLabel.getValue(),
jobNameLable.getValue(), convertToCronExpressionStr(cronExp));
jobSchedulerModel.saveJobConfigurationAndReschedule(
jobGroup.getValue(), jobName.getValue(), cronExpression);
cronExpressionInputPopup.close();
getJobSchedulingRenderer();
Util.reloadBindings(jobSchedulerGrid);
}
private String convertToCronExpressionStr(CronExpression cronExp) {
return String.format("%1$s %2$s %3$s %4$s %5$s %6$s %7$s",
cronExp.getSeconds(), cronExp.getMinutes(), cronExp.getHours(),
cronExp.getDayOfMonth(), cronExp.getMonth(),
cronExp.getDayOfWeek(), cronExp.getYear()).trim();
private String getCronExpressionString() {
String cronExpression = "";
cronExpression += StringUtils.trimToEmpty(cronExpressionSeconds.getValue()) + " ";
cronExpression += StringUtils.trimToEmpty(cronExpressionMinutes.getValue()) + " ";
cronExpression += StringUtils.trimToEmpty(cronExpressionHours.getValue()) + " ";
cronExpression += StringUtils.trimToEmpty(cronExpressionDayOfMonth.getValue()) + " ";
cronExpression += StringUtils.trimToEmpty(cronExpressionMonth.getValue()) + " ";
cronExpression += StringUtils.trimToEmpty(cronExpressionDayOfWeek.getValue());
String year = StringUtils.trimToEmpty(cronExpressionYear.getValue());
if (!year.isEmpty()) {
cronExpression += " " + year;
}
return cronExpression;
}
public void cancel() {
cronExpressionInputPopup.invalidate();
cronExpressionInputPopup.close();
}
/**
* Class representing cron expression
*/
public class CronExpression {
private String seconds;
private String minutes;
private String hours;
private String dayOfMonth;
private String month;
private String dayOfWeek;
private String year;
public String getSeconds() {
return seconds;
}
public void setSeconds(String seconds) {
Validate.notEmpty(seconds, "Seconds is mandatory");
this.seconds = seconds;
}
public String getMinutes() {
return minutes;
}
public void setMinutes(String minutes) {
Validate.notEmpty(minutes, "Minutes is mandatory");
this.minutes = minutes;
}
public String getHours() {
return hours;
}
public void setHours(String hours) {
Validate.notEmpty(hours, "Hours is mandatory");
this.hours = hours;
}
public String getDayOfMonth() {
return dayOfMonth;
}
public void setDayOfMonth(String dayOfMonth) {
Validate.notEmpty(dayOfMonth, "day of month is mandatory");
this.dayOfMonth = dayOfMonth;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
Validate.notEmpty(month, "month is mandatory");
this.month = month;
}
public String getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(String dayOfWeek) {
Validate.notEmpty(dayOfWeek, "day of week is mandatory");
this.dayOfWeek = dayOfWeek;
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
}
}

View file

@ -0,0 +1,97 @@
/*
* This file is part of LibrePlan
*
* Copyright (C) 2013 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.web.common;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
import org.libreplan.importers.ISchedulerManager;
import org.libreplan.importers.SchedulerInfo;
import org.libreplan.web.common.concurrentdetection.OnConcurrentModification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Model for UI operations related to {@link JobSchedulerConfiguration}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@OnConcurrentModification(goToPage = "/common/job_scheduler_configuration.zul")
public class JobSchedulerModel implements IJobSchedulerModel {
@Autowired
private ISchedulerManager schedulerManager;
@Autowired
private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO;
@Override
public List<SchedulerInfo> getSchedulerInfos() {
List<SchedulerInfo> schedulerInfoList = schedulerManager
.getSchedulerInfos();
Collections.sort(schedulerInfoList, new Comparator<SchedulerInfo>() {
@Override
public int compare(SchedulerInfo o1, SchedulerInfo o2) {
int result = o1
.getJobSchedulerConfiguration()
.getJobGroup()
.compareTo(
o2.getJobSchedulerConfiguration().getJobGroup());
if (result == 0) {
result = o1
.getJobSchedulerConfiguration()
.getJobName()
.compareTo(
o2.getJobSchedulerConfiguration()
.getJobName());
}
return result;
}
});
return schedulerInfoList;
}
@Override
public void doManual(SchedulerInfo schedulerInfo) {
schedulerManager.doManual(schedulerInfo.getJobSchedulerConfiguration()
.getJobName());
}
@Override
@Transactional
public void saveJobConfigurationAndReschedule(String jobGroup,
String jobName, String cronExp) {
JobSchedulerConfiguration jobSchedulerConfiguration = jobSchedulerConfigurationDAO
.findByJobGroupAndJobName(jobGroup, jobName);
jobSchedulerConfiguration.setCronExpression(cronExp);
jobSchedulerConfigurationDAO.save(jobSchedulerConfiguration);
schedulerManager.rescheduleJob(jobSchedulerConfiguration);
}
}

View file

@ -52,9 +52,12 @@
<!-- Scheduler -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" />
<bean class="org.libreplan.importers.SchedulerManager" init-method="scheduleJobs">
<property name="scheduler" ref="schedulerFactoryBean"/>
</bean>
<bean id="schedulerManager"
class="org.libreplan.importers.SchedulerManager"
scope="singleton"
init-method="scheduleJobs">
<property name="scheduler" ref="schedulerFactoryBean"/>
</bean>
<context:component-scan base-package="org.libreplan"/>

View file

@ -17,107 +17,108 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<?page title="${i18n:_('LibrePlan: Job scheduler')}"?>
<?page id="exceptionDayTypesList" title="${i18n:_('LibrePlan: Job scheduler')}" ?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<?page id="List"?>
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/common/layout/template.zul"?>
<?link rel="shortcut icon" href="/common/img/favicon.ico" type="image/x-icon"?>
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan.css"?>
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan_zk.css"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<zk>
<window id="jobSchedulerWindow" self="@{define(content)}"
apply="org.libreplan.web.common.JobSchedulerController"
title="${i18n:_('Job scheduling')}">
<grid id="jobSchedulerGrid" sizedByContent="false" height="200px"
rowRenderer="@{jobSchedulerController.jobSchedulingRenderer}">
<?component name="list" inline="true" macroURI="_listStretchesFunctionTemplates.zul"?>
<?component name="edit" inline="true" macroURI="_editStretchesFunctionTemplate.zul"?>
<zk>
<window self="@{define(content)}"
apply="org.libreplan.web.common.JobSchedulerController">
<grid id="jobSchedulerGrid"
rowRenderer="@{jobSchedulerController.jobSchedulingRenderer}"
model="@{jobSchedulerController.schedulerInfos}">
<columns>
<column label="${i18n:_('Job group')}" width="140px" />
<column label="${i18n:_('Job group')}" />
<column label="${i18n:_('Job name')}" />
<column label="${i18n:_('Cron expression')}" width="160px"/>
<column label="${i18n:_('Cron expression')}" />
<column label="${i18n:_('Next fire time')}" />
<column label="${i18n:_('Action')}" width="100px"/>
<column label="${i18n:_('Action')}" />
</columns>
</grid>
<div>
<groupbox closable="false" >
<caption label="Cron expression format" />
<grid width="400px">
<columns>
<column label="${i18n:_('Field')}" width="40%"/>
<column label="${i18n:_('Allowed values')}" />
</columns>
<rows>
<row>
<label value="Seconds"/>
<label value="0-59"/>
</row>
<row>
<label value="Minutes"/>
<label value="0-59"/>
</row>
<row>
<label value="Hours"/>
<label value="0-23"/>
</row>
<row>
<label value="Day of month"/>
<label value="1-31"/>
</row>
<row>
<label value="Month"/>
<label value="1-12(or names[Jan-Dec])"/>
</row>
<row>
<label value="Day of week"/>
<label value="0-7 (0 or 7 is Sun, or use names[Sun-sat])"/>
</row>
<row>
<label value="Year(optional)"/>
<label value="1970-2099"/>
</row>
</rows>
</grid>
<div>
<span>${i18n:_("For more details on cron expression click")}</span>
<a class="command" href="http://www.manpagez.com/man/5/crontab/">${i18n:_('this')}</a>
</div>
<caption label="${i18n:_('Cron expression format')}" />
<grid width="500px">
<columns>
<column label="${i18n:_('Field')}" width="150px"/>
<column label="${i18n:_('Allowed values')}" />
</columns>
<rows>
<row>
<label value="${i18n:_('Seconds')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Minutes')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Hours')}"/>
<label value="0-23"/>
</row>
<row>
<label value="${i18n:_('Day of month')}"/>
<label value="1-31"/>
</row>
<row>
<label value="${i18n:_('Month')}"/>
<span>1-12 (<label value="${i18n:_('or names')}"/> [JAN-DEC])</span>
</row>
<row>
<label value="${i18n:_('Day of week')}"/>
<span>0-7 (<label value="${i18n:_('0 or 7 is Sunday, or use names')}"/> [SUN-SAT])</span>
</row>
<row>
<label value="${i18n:_('Year (optional)')}"/>
<label value="1970-2099"/>
</row>
</rows>
</grid>
<div>
${i18n:_("For more details on cron expression click")}
<a href="http://www.manpagez.com/man/5/crontab/">${i18n:_('here')}.</a>
</div>
</groupbox>
</div>
<popup id="cronExpressionInputPopup" width="380px">
<label id="jobGroup" value="@{jobSchedulerController.schedulerInfo}"/>
<label value="," />
<label id="jobName" value="@{jobSchedulerController.schedulerInfo}"/>
<grid id="cronExpressionGrid" sizedByContent="false" height="50px">
<columns>
<column label="${i18n:_('sec')}"/>
<column label="${i18n:_('min')}" />
<column label="${i18n:_('hrs')}"/>
<column label="${i18n:_('day of month')}" width="70px"/>
<column label="${i18n:_('month')}"/>
<column label="${i18n:_('day of week')}" width="70px"/>
<column label="${i18n:_('year')}" width="50px"/>
</columns>
<rows>
<row self="@{each=cronExpression}" value="@{cronExpression}">
<textbox value="@{cronExpression.seconds}" />
<textbox value="@{cronExpression.minutes}" />
<textbox value="@{cronExpression.hours}" />
<textbox value="@{cronExpression.dayOfMonth}" />
<textbox value="@{cronExpression.month}" />
<textbox value="@{cronExpression.dayOfWeek}" />
<textbox value="@{cronExpression.year}" />
</row>
<popup id="cronExpressionInputPopup" width="525px">
<label id="jobGroup" value="@{jobSchedulerController.schedulerInfo}"/>
-
<label id="jobName" value="@{jobSchedulerController.schedulerInfo}"/>
<grid id="cronExpressionGrid">
<columns>
<column label="${i18n:_('Seconds')}"/>
<column label="${i18n:_('Minutes')}" />
<column label="${i18n:_('Hours')}"/>
<column label="${i18n:_('Day of month')}"/>
<column label="${i18n:_('Month')}"/>
<column label="${i18n:_('Day of week')}"/>
<column label="${i18n:_('Year')}"/>
</columns>
<rows>
<row>
<textbox id="cronExpressionSeconds" width="60px" />
<textbox id="cronExpressionMinutes" width="60px" />
<textbox id="cronExpressionHours" width="60px" />
<textbox id="cronExpressionDayOfMonth" width="60px" />
<textbox id="cronExpressionMonth" width="60px" />
<textbox id="cronExpressionDayOfWeek" width="60px" />
<textbox id="cronExpressionYear" width="60px" />
</row>
</rows>
</grid>
</grid>
<button onClick="jobSchedulerController.reschedule();" label="${i18n:_('OK')}" />
<button onClick="jobSchedulerController.cancel();" label="${i18n:_('Cancel')}" />
</popup>
</window>
</zk>