mirror of https://github.com/portainer/portainer
* feat(UX): schedule creation UX overhaul (#2485)
* feat(api): add a new Recurring property on Schedule * feat(schedules): date to cron convert + recurring flag * feat(schedules): update angularjs-datetime-picker from v1 to v2 * chore(app): use minified dependency for angularjs-datetime-picker * chore(vendor): rollback version of angularjs-datetime-picker * * feat(ux): replace datepicker for schedule creation/details * feat(container-stats): add refresh rate of 1 and 3 seconds (#2493) * fix(templates): set var to default value if no value selected (#2323) * fix(templates): set preset to true iff var type is preset * fix(templates): add env var value when changing type * feat(security): shutdown instance after 5minutes if no admin account created (#2500) * feat(security): skip admin check if --no-auth * fix(security): change error message * fix(vendor): use datepicker minified version * feat(schedule-creation): replace angular-datetime-picker * feat(schedule): parse cron to datetime * fix(schedule): fix zero based monthspull/2449/head
parent
9e1800e2ec
commit
1a94158f77
|
@ -136,6 +136,7 @@ func loadSnapshotSystemSchedule(jobScheduler portainer.JobScheduler, snapshotter
|
||||||
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
|
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
|
||||||
Name: "system_snapshot",
|
Name: "system_snapshot",
|
||||||
CronExpression: "@every " + *flags.SnapshotInterval,
|
CronExpression: "@every " + *flags.SnapshotInterval,
|
||||||
|
Recurring: true,
|
||||||
JobType: portainer.SnapshotJobType,
|
JobType: portainer.SnapshotJobType,
|
||||||
SnapshotJob: snapshotJob,
|
SnapshotJob: snapshotJob,
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
|
@ -174,6 +175,7 @@ func loadEndpointSyncSystemSchedule(jobScheduler portainer.JobScheduler, schedul
|
||||||
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
|
ID: portainer.ScheduleID(scheduleService.GetNextIdentifier()),
|
||||||
Name: "system_endpointsync",
|
Name: "system_endpointsync",
|
||||||
CronExpression: "@every " + *flags.SyncInterval,
|
CronExpression: "@every " + *flags.SyncInterval,
|
||||||
|
Recurring: true,
|
||||||
JobType: portainer.EndpointSyncJobType,
|
JobType: portainer.EndpointSyncJobType,
|
||||||
EndpointSyncJob: endpointSyncJob,
|
EndpointSyncJob: endpointSyncJob,
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
|
|
|
@ -9,8 +9,9 @@ import (
|
||||||
|
|
||||||
// ScriptExecutionJobRunner is used to run a ScriptExecutionJob
|
// ScriptExecutionJobRunner is used to run a ScriptExecutionJob
|
||||||
type ScriptExecutionJobRunner struct {
|
type ScriptExecutionJobRunner struct {
|
||||||
schedule *portainer.Schedule
|
schedule *portainer.Schedule
|
||||||
context *ScriptExecutionJobContext
|
context *ScriptExecutionJobContext
|
||||||
|
executedOnce bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScriptExecutionJobContext represents the context of execution of a ScriptExecutionJob
|
// ScriptExecutionJobContext represents the context of execution of a ScriptExecutionJob
|
||||||
|
@ -32,8 +33,9 @@ func NewScriptExecutionJobContext(jobService portainer.JobService, endpointServi
|
||||||
// NewScriptExecutionJobRunner returns a new runner that can be scheduled
|
// NewScriptExecutionJobRunner returns a new runner that can be scheduled
|
||||||
func NewScriptExecutionJobRunner(schedule *portainer.Schedule, context *ScriptExecutionJobContext) *ScriptExecutionJobRunner {
|
func NewScriptExecutionJobRunner(schedule *portainer.Schedule, context *ScriptExecutionJobContext) *ScriptExecutionJobRunner {
|
||||||
return &ScriptExecutionJobRunner{
|
return &ScriptExecutionJobRunner{
|
||||||
schedule: schedule,
|
schedule: schedule,
|
||||||
context: context,
|
context: context,
|
||||||
|
executedOnce: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +43,11 @@ func NewScriptExecutionJobRunner(schedule *portainer.Schedule, context *ScriptEx
|
||||||
// It will iterate through all the endpoints specified in the context to
|
// It will iterate through all the endpoints specified in the context to
|
||||||
// execute the script associated to the job.
|
// execute the script associated to the job.
|
||||||
func (runner *ScriptExecutionJobRunner) Run() {
|
func (runner *ScriptExecutionJobRunner) Run() {
|
||||||
|
if !runner.schedule.Recurring && runner.executedOnce {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runner.executedOnce = true
|
||||||
|
|
||||||
scriptFile, err := runner.context.fileService.GetFileContent(runner.schedule.ScriptExecutionJob.ScriptPath)
|
scriptFile, err := runner.context.fileService.GetFileContent(runner.schedule.ScriptExecutionJob.ScriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("scheduled job error (script execution). Unable to retrieve script file (err=%s)\n", err)
|
log.Printf("scheduled job error (script execution). Unable to retrieve script file (err=%s)\n", err)
|
||||||
|
|
|
@ -18,6 +18,7 @@ type scheduleCreateFromFilePayload struct {
|
||||||
Name string
|
Name string
|
||||||
Image string
|
Image string
|
||||||
CronExpression string
|
CronExpression string
|
||||||
|
Recurring bool
|
||||||
Endpoints []portainer.EndpointID
|
Endpoints []portainer.EndpointID
|
||||||
File []byte
|
File []byte
|
||||||
RetryCount int
|
RetryCount int
|
||||||
|
@ -27,6 +28,7 @@ type scheduleCreateFromFilePayload struct {
|
||||||
type scheduleCreateFromFileContentPayload struct {
|
type scheduleCreateFromFileContentPayload struct {
|
||||||
Name string
|
Name string
|
||||||
CronExpression string
|
CronExpression string
|
||||||
|
Recurring bool
|
||||||
Image string
|
Image string
|
||||||
Endpoints []portainer.EndpointID
|
Endpoints []portainer.EndpointID
|
||||||
FileContent string
|
FileContent string
|
||||||
|
@ -174,9 +176,8 @@ func (handler *Handler) createScheduleObjectFromFilePayload(payload *scheduleCre
|
||||||
scheduleIdentifier := portainer.ScheduleID(handler.ScheduleService.GetNextIdentifier())
|
scheduleIdentifier := portainer.ScheduleID(handler.ScheduleService.GetNextIdentifier())
|
||||||
|
|
||||||
job := &portainer.ScriptExecutionJob{
|
job := &portainer.ScriptExecutionJob{
|
||||||
Endpoints: payload.Endpoints,
|
Endpoints: payload.Endpoints,
|
||||||
Image: payload.Image,
|
Image: payload.Image,
|
||||||
// ScheduleID: scheduleIdentifier,
|
|
||||||
RetryCount: payload.RetryCount,
|
RetryCount: payload.RetryCount,
|
||||||
RetryInterval: payload.RetryInterval,
|
RetryInterval: payload.RetryInterval,
|
||||||
}
|
}
|
||||||
|
@ -185,6 +186,7 @@ func (handler *Handler) createScheduleObjectFromFilePayload(payload *scheduleCre
|
||||||
ID: scheduleIdentifier,
|
ID: scheduleIdentifier,
|
||||||
Name: payload.Name,
|
Name: payload.Name,
|
||||||
CronExpression: payload.CronExpression,
|
CronExpression: payload.CronExpression,
|
||||||
|
Recurring: payload.Recurring,
|
||||||
JobType: portainer.ScriptExecutionJobType,
|
JobType: portainer.ScriptExecutionJobType,
|
||||||
ScriptExecutionJob: job,
|
ScriptExecutionJob: job,
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
|
@ -197,9 +199,8 @@ func (handler *Handler) createScheduleObjectFromFileContentPayload(payload *sche
|
||||||
scheduleIdentifier := portainer.ScheduleID(handler.ScheduleService.GetNextIdentifier())
|
scheduleIdentifier := portainer.ScheduleID(handler.ScheduleService.GetNextIdentifier())
|
||||||
|
|
||||||
job := &portainer.ScriptExecutionJob{
|
job := &portainer.ScriptExecutionJob{
|
||||||
Endpoints: payload.Endpoints,
|
Endpoints: payload.Endpoints,
|
||||||
Image: payload.Image,
|
Image: payload.Image,
|
||||||
// ScheduleID: scheduleIdentifier,
|
|
||||||
RetryCount: payload.RetryCount,
|
RetryCount: payload.RetryCount,
|
||||||
RetryInterval: payload.RetryInterval,
|
RetryInterval: payload.RetryInterval,
|
||||||
}
|
}
|
||||||
|
@ -208,6 +209,7 @@ func (handler *Handler) createScheduleObjectFromFileContentPayload(payload *sche
|
||||||
ID: scheduleIdentifier,
|
ID: scheduleIdentifier,
|
||||||
Name: payload.Name,
|
Name: payload.Name,
|
||||||
CronExpression: payload.CronExpression,
|
CronExpression: payload.CronExpression,
|
||||||
|
Recurring: payload.Recurring,
|
||||||
JobType: portainer.ScriptExecutionJobType,
|
JobType: portainer.ScriptExecutionJobType,
|
||||||
ScriptExecutionJob: job,
|
ScriptExecutionJob: job,
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
|
|
|
@ -17,6 +17,7 @@ type scheduleUpdatePayload struct {
|
||||||
Name *string
|
Name *string
|
||||||
Image *string
|
Image *string
|
||||||
CronExpression *string
|
CronExpression *string
|
||||||
|
Recurring *bool
|
||||||
Endpoints []portainer.EndpointID
|
Endpoints []portainer.EndpointID
|
||||||
FileContent *string
|
FileContent *string
|
||||||
RetryCount *int
|
RetryCount *int
|
||||||
|
@ -101,6 +102,11 @@ func updateSchedule(schedule *portainer.Schedule, payload *scheduleUpdatePayload
|
||||||
updateJobSchedule = true
|
updateJobSchedule = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.Recurring != nil {
|
||||||
|
schedule.Recurring = *payload.Recurring
|
||||||
|
updateJobSchedule = true
|
||||||
|
}
|
||||||
|
|
||||||
if payload.Image != nil {
|
if payload.Image != nil {
|
||||||
schedule.ScriptExecutionJob.Image = *payload.Image
|
schedule.ScriptExecutionJob.Image = *payload.Image
|
||||||
updateJobSchedule = true
|
updateJobSchedule = true
|
||||||
|
|
|
@ -244,11 +244,13 @@ type (
|
||||||
|
|
||||||
// Schedule represents a scheduled job.
|
// Schedule represents a scheduled job.
|
||||||
// It only contains a pointer to one of the JobRunner implementations
|
// It only contains a pointer to one of the JobRunner implementations
|
||||||
// based on the JobType
|
// based on the JobType.
|
||||||
|
// NOTE: The Recurring option is only used by ScriptExecutionJob at the moment
|
||||||
Schedule struct {
|
Schedule struct {
|
||||||
ID ScheduleID `json:"Id"`
|
ID ScheduleID `json:"Id"`
|
||||||
Name string
|
Name string
|
||||||
CronExpression string
|
CronExpression string
|
||||||
|
Recurring bool
|
||||||
Created int64
|
Created int64
|
||||||
JobType JobType
|
JobType JobType
|
||||||
ScriptExecutionJob *ScriptExecutionJob
|
ScriptExecutionJob *ScriptExecutionJob
|
||||||
|
|
|
@ -23,4 +23,6 @@ angular.module('portainer', [
|
||||||
'portainer.azure',
|
'portainer.azure',
|
||||||
'portainer.docker',
|
'portainer.docker',
|
||||||
'extension.storidge',
|
'extension.storidge',
|
||||||
'rzModule']);
|
'rzModule',
|
||||||
|
'moment-picker'
|
||||||
|
]);
|
||||||
|
|
|
@ -7,6 +7,38 @@ angular.module('portainer.app').component('scheduleForm', {
|
||||||
formValidationError: ''
|
formValidationError: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ctrl.scheduleValues = [{
|
||||||
|
displayed: 'Every hour',
|
||||||
|
cron: '0 0 * * *'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayed: 'Every 2 hours',
|
||||||
|
cron: '0 0 0/2 * *'
|
||||||
|
}, {
|
||||||
|
displayed: 'Every day',
|
||||||
|
cron: '0 0 0 * *'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
ctrl.formValues = {
|
||||||
|
datetime: ctrl.model.CronExpression ? cronToDatetime(ctrl.model.CronExpression) : moment(),
|
||||||
|
scheduleValue: ctrl.scheduleValues[0],
|
||||||
|
cronMethod: 'basic'
|
||||||
|
};
|
||||||
|
|
||||||
|
function cronToDatetime(cron) {
|
||||||
|
strings = cron.split(' ');
|
||||||
|
if (strings.length !== 5) {
|
||||||
|
return moment();
|
||||||
|
}
|
||||||
|
return moment(cron, 's m H D M');
|
||||||
|
}
|
||||||
|
|
||||||
|
function datetimeToCron(datetime) {
|
||||||
|
var date = moment(datetime);
|
||||||
|
return '0 '.concat(date.minutes(), ' ', date.hours(), ' ', date.date(), ' ', (date.month() + 1));
|
||||||
|
}
|
||||||
|
|
||||||
this.action = function() {
|
this.action = function() {
|
||||||
ctrl.state.formValidationError = '';
|
ctrl.state.formValidationError = '';
|
||||||
|
|
||||||
|
@ -15,6 +47,15 @@ angular.module('portainer.app').component('scheduleForm', {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctrl.formValues.cronMethod === 'basic') {
|
||||||
|
if (ctrl.model.Recurring === false) {
|
||||||
|
ctrl.model.CronExpression = datetimeToCron(ctrl.formValues.datetime);
|
||||||
|
} else {
|
||||||
|
ctrl.model.CronExpression = ctrl.formValues.scheduleValue.cron;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctrl.model.Recurring = true;
|
||||||
|
}
|
||||||
ctrl.formAction();
|
ctrl.formAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,24 +18,107 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !name-input -->
|
<!-- !name-input -->
|
||||||
<!-- cron-input -->
|
<!-- cron-input -->
|
||||||
<div class="form-group">
|
<!-- schedule-method-select -->
|
||||||
<label for="schedule_cron" class="col-sm-1 control-label text-left">Cron rule</label>
|
<div class="col-sm-12 form-section-title">
|
||||||
<div class="col-sm-11">
|
Schedule configuration
|
||||||
<input type="text" class="form-control" ng-model="$ctrl.model.CronExpression" id="schedule_cron" name="schedule_cron" placeholder="0 2 * * *" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" ng-show="scheduleForm.schedule_cron.$invalid">
|
<div class="form-group"></div>
|
||||||
<div class="col-sm-12 small text-warning">
|
<div class="form-group" style="margin-bottom: 0">
|
||||||
<div ng-messages="scheduleForm.schedule_cron.$error">
|
<div class="boxselector_wrapper">
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
<div>
|
||||||
|
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic">
|
||||||
|
<label for="config_basic">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-calendar-alt" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Basic configuration
|
||||||
|
</div>
|
||||||
|
<p>Select date from calendar</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced">
|
||||||
|
<label for="config_advanced">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
Advanced configuration
|
||||||
|
</div>
|
||||||
|
<p>Write your own cron rule</p>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- !schedule-method-select -->
|
||||||
<span class="col-sm-12 text-muted small">
|
<!-- basic-schedule -->
|
||||||
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information about the supported cron expression format.
|
<div ng-if="$ctrl.formValues.cronMethod === 'basic'">
|
||||||
</span>
|
<div class="form-group">
|
||||||
|
<label for="recurring" class="col-sm-2 control-label text-left">Recurring schedule</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" name="recurring" ng-model="$ctrl.model.Recurring"><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- not-recurring -->
|
||||||
|
<div ng-if="!$ctrl.model.Recurring">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="schedule_cron" class="col-sm-2 control-label text-left">Schedule date</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" moment-picker ng-model="$ctrl.formValues.datetime" format="YYYY-MM-DD HH:mm">
|
||||||
|
</div>
|
||||||
|
<div ng-show="scheduleForm.datepicker.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="scheduleForm.datepicker.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !not-recurring -->
|
||||||
|
<!-- recurring -->
|
||||||
|
<div ng-if="$ctrl.model.Recurring">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="schedule_value" class="col-sm-2 control-label text-left">Schedule time</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select id="schedule_value" name="schedule_value" class="form-control"
|
||||||
|
ng-model="$ctrl.formValues.scheduleValue" ng-options="value.displayed for value in $ctrl.scheduleValues" required
|
||||||
|
></select>
|
||||||
|
</div>
|
||||||
|
<div ng-show="scheduleForm.schedule_value.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="scheduleForm.schedule_value.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !recurring -->
|
||||||
</div>
|
</div>
|
||||||
|
<!-- !basic-schedule -->
|
||||||
|
<!-- advanced-schedule -->
|
||||||
|
<div ng-if="$ctrl.formValues.cronMethod === 'advanced'">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="schedule_cron" class="col-sm-2 control-label text-left">Cron rule</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" ng-model="$ctrl.model.CronExpression" id="schedule_cron" name="schedule_cron"
|
||||||
|
placeholder="0 2 * * *" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="scheduleForm.schedule_cron.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="scheduleForm.schedule_cron.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<span class="col-sm-12 text-muted small">
|
||||||
|
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information about the supported cron expression format.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !advanced-schedule -->
|
||||||
<!-- !cron-input -->
|
<!-- !cron-input -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Job configuration
|
Job configuration
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
function ScheduleDefaultModel() {
|
function ScheduleDefaultModel() {
|
||||||
this.Name = '';
|
this.Name = '';
|
||||||
|
this.Recurring = false;
|
||||||
this.CronExpression = '';
|
this.CronExpression = '';
|
||||||
this.JobType = 1;
|
this.JobType = 1;
|
||||||
this.Job = new ScriptExecutionDefaultJobModel();
|
this.Job = new ScriptExecutionDefaultJobModel();
|
||||||
|
@ -16,6 +17,7 @@ function ScriptExecutionDefaultJobModel() {
|
||||||
function ScheduleModel(data) {
|
function ScheduleModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.Name = data.Name;
|
this.Name = data.Name;
|
||||||
|
this.Recurring = data.Recurring;
|
||||||
this.JobType = data.JobType;
|
this.JobType = data.JobType;
|
||||||
this.CronExpression = data.CronExpression;
|
this.CronExpression = data.CronExpression;
|
||||||
this.Created = data.Created;
|
this.Created = data.Created;
|
||||||
|
@ -42,6 +44,7 @@ function ScriptExecutionTaskModel(data) {
|
||||||
|
|
||||||
function ScheduleCreateRequest(model) {
|
function ScheduleCreateRequest(model) {
|
||||||
this.Name = model.Name;
|
this.Name = model.Name;
|
||||||
|
this.Recurring = model.Recurring;
|
||||||
this.CronExpression = model.CronExpression;
|
this.CronExpression = model.CronExpression;
|
||||||
this.Image = model.Job.Image;
|
this.Image = model.Job.Image;
|
||||||
this.Endpoints = model.Job.Endpoints;
|
this.Endpoints = model.Job.Endpoints;
|
||||||
|
@ -54,6 +57,7 @@ function ScheduleCreateRequest(model) {
|
||||||
function ScheduleUpdateRequest(model) {
|
function ScheduleUpdateRequest(model) {
|
||||||
this.id = model.Id;
|
this.id = model.Id;
|
||||||
this.Name = model.Name;
|
this.Name = model.Name;
|
||||||
|
this.Recurring = model.Recurring;
|
||||||
this.CronExpression = model.CronExpression;
|
this.CronExpression = model.CronExpression;
|
||||||
this.Image = model.Job.Image;
|
this.Image = model.Job.Image;
|
||||||
this.Endpoints = model.Job.Endpoints;
|
this.Endpoints = model.Job.Endpoints;
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
|
|
||||||
<!-- build:js js/app.js -->
|
<!-- build:js js/app.js -->
|
||||||
<script src="js/angular.js"></script>
|
|
||||||
<script src="js/vendor.js"></script>
|
<script src="js/vendor.js"></script>
|
||||||
|
<script src="js/angular.js"></script>
|
||||||
<script src="js/portainer.js"></script>
|
<script src="js/portainer.js"></script>
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"angular-local-storage": "~0.5.2",
|
"angular-local-storage": "~0.5.2",
|
||||||
"angular-messages": "~1.5.0",
|
"angular-messages": "~1.5.0",
|
||||||
"angular-mocks": "~1.5.0",
|
"angular-mocks": "~1.5.0",
|
||||||
|
"angular-moment-picker": "^0.10.2",
|
||||||
"angular-resource": "~1.5.0",
|
"angular-resource": "~1.5.0",
|
||||||
"angular-sanitize": "~1.5.0",
|
"angular-sanitize": "~1.5.0",
|
||||||
"angular-ui-bootstrap": "~2.5.0",
|
"angular-ui-bootstrap": "~2.5.0",
|
||||||
|
|
|
@ -32,6 +32,7 @@ css:
|
||||||
- 'codemirror/addon/lint/lint.css'
|
- 'codemirror/addon/lint/lint.css'
|
||||||
- 'angular-json-tree/dist/angular-json-tree.css'
|
- 'angular-json-tree/dist/angular-json-tree.css'
|
||||||
- 'angular-loading-bar/build/loading-bar.css'
|
- 'angular-loading-bar/build/loading-bar.css'
|
||||||
|
- 'angular-moment-picker/dist/angular-moment-picker.min.css'
|
||||||
angular:
|
angular:
|
||||||
- 'angular/angular.js'
|
- 'angular/angular.js'
|
||||||
- 'angular-ui-bootstrap/dist/ui-bootstrap-tpls.js'
|
- 'angular-ui-bootstrap/dist/ui-bootstrap-tpls.js'
|
||||||
|
@ -53,3 +54,4 @@ angular:
|
||||||
- 'angularjs-scroll-glue/src/scrollglue.js'
|
- 'angularjs-scroll-glue/src/scrollglue.js'
|
||||||
- 'angular-clipboard/angular-clipboard.js'
|
- 'angular-clipboard/angular-clipboard.js'
|
||||||
- 'angular-file-saver/dist/angular-file-saver.bundle.js'
|
- 'angular-file-saver/dist/angular-file-saver.bundle.js'
|
||||||
|
- 'angular-moment-picker/dist/angular-moment-picker.min.js'
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -136,6 +136,14 @@ angular-mocks@~1.5.0:
|
||||||
version "1.5.11"
|
version "1.5.11"
|
||||||
resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.5.11.tgz#a0e1dd0ea55fd77ee7a757d75536c5e964c86f81"
|
resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.5.11.tgz#a0e1dd0ea55fd77ee7a757d75536c5e964c86f81"
|
||||||
|
|
||||||
|
angular-moment-picker@^0.10.2:
|
||||||
|
version "0.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/angular-moment-picker/-/angular-moment-picker-0.10.2.tgz#54c8b3c228b33dffa3b7b3d0773a585323815c33"
|
||||||
|
integrity sha512-WvmrQM0zEcFqi50yDELaF34Ilrx4PtL7mWLcpTZCJGQDvMlIsxJrB30LxOkoJv8yrrLxD2s6nnR3t1/SqioWWw==
|
||||||
|
dependencies:
|
||||||
|
angular "^1.3"
|
||||||
|
moment "^2.16.0"
|
||||||
|
|
||||||
angular-resource@~1.5.0:
|
angular-resource@~1.5.0:
|
||||||
version "1.5.11"
|
version "1.5.11"
|
||||||
resolved "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.5.11.tgz#d93ea619184a2e0ee3ae338265758363172929f0"
|
resolved "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.5.11.tgz#d93ea619184a2e0ee3ae338265758363172929f0"
|
||||||
|
@ -156,6 +164,11 @@ angular@1.x, angular@~1.5.0:
|
||||||
version "1.5.11"
|
version "1.5.11"
|
||||||
resolved "https://registry.yarnpkg.com/angular/-/angular-1.5.11.tgz#8c5ba7386f15965c9acf3429f6881553aada30d6"
|
resolved "https://registry.yarnpkg.com/angular/-/angular-1.5.11.tgz#8c5ba7386f15965c9acf3429f6881553aada30d6"
|
||||||
|
|
||||||
|
angular@^1.3:
|
||||||
|
version "1.7.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/angular/-/angular-1.7.5.tgz#d1c1c01c6f5dc835638f3f9aa51012857bdac49e"
|
||||||
|
integrity sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==
|
||||||
|
|
||||||
angularjs-scroll-glue@^2.2.0:
|
angularjs-scroll-glue@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/angularjs-scroll-glue/-/angularjs-scroll-glue-2.2.0.tgz#07d3399ac16ca874c63b6b5ee2ee30558b37e5d1"
|
resolved "https://registry.yarnpkg.com/angularjs-scroll-glue/-/angularjs-scroll-glue-2.2.0.tgz#07d3399ac16ca874c63b6b5ee2ee30558b37e5d1"
|
||||||
|
@ -2905,6 +2918,11 @@ moment@^2.10.6:
|
||||||
version "2.14.1"
|
version "2.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.14.1.tgz#b35b27c47e57ed2ddc70053d6b07becdb291741c"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.14.1.tgz#b35b27c47e57ed2ddc70053d6b07becdb291741c"
|
||||||
|
|
||||||
|
moment@^2.16.0:
|
||||||
|
version "2.22.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
|
||||||
|
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
|
||||||
|
|
||||||
moment@^2.21.0:
|
moment@^2.21.0:
|
||||||
version "2.21.0"
|
version "2.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"
|
||||||
|
|
Loading…
Reference in New Issue