diff --git a/app/edge/__module.js b/app/edge/__module.js
index 433dd6214..be1da9d61 100644
--- a/app/edge/__module.js
+++ b/app/edge/__module.js
@@ -122,7 +122,7 @@ angular
url: '/new',
views: {
'content@': {
- component: 'createEdgeJobView',
+ component: 'edgeJobsCreateView',
},
},
};
diff --git a/app/edge/components/edge-job-form/edgeJobForm.html b/app/edge/components/edge-job-form/edgeJobForm.html
index 5af164f2d..93fd17f9c 100644
--- a/app/edge/components/edge-job-form/edgeJobForm.html
+++ b/app/edge/components/edge-job-form/edgeJobForm.html
@@ -36,7 +36,7 @@
Edge job configuration
-
+
@@ -44,9 +44,10 @@
@@ -135,7 +136,7 @@
diff --git a/app/edge/components/edge-job-form/edgeJobFormController.js b/app/edge/components/edge-job-form/edgeJobFormController.js
index 0bc8acfaf..814eb19bb 100644
--- a/app/edge/components/edge-job-form/edgeJobFormController.js
+++ b/app/edge/components/edge-job-form/edgeJobFormController.js
@@ -9,8 +9,8 @@ export class EdgeJobFormController {
this.$scope = $scope;
this.$async = $async;
- this.cronMethods = cronMethodOptions;
- this.buildMethods = [editor, upload];
+ this.cronMethods = cronMethodOptions.map((o) => ({ ...o, id: o.id + '-old' }));
+ this.buildMethods = [editor, upload].map((o) => ({ ...o, id: o.id + '-old' }));
this.state = {
formValidationError: '',
@@ -70,10 +70,12 @@ export class EdgeJobFormController {
onChangeModel(model) {
const defaultTime = moment().add('hours', 1);
+ const scheduled = this.scheduleValues.find((v) => v.cron === model.CronExpression);
+
this.formValues = {
datetime: model.CronExpression ? cronToDatetime(model.CronExpression, defaultTime) : defaultTime,
- scheduleValue: this.formValues.scheduleValue,
- cronMethod: model.Recurring ? 'advanced' : 'basic',
+ scheduleValue: scheduled || this.scheduleValues[0],
+ cronMethod: model.Recurring && !scheduled ? 'advanced' : 'basic',
method: this.formValues.method,
};
}
diff --git a/app/edge/react/views/jobs.ts b/app/edge/react/views/jobs.ts
index 6042aa7be..7718cffba 100644
--- a/app/edge/react/views/jobs.ts
+++ b/app/edge/react/views/jobs.ts
@@ -4,10 +4,12 @@ import { r2a } from '@/react-tools/react2angular';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withUIRouter } from '@/react-tools/withUIRouter';
import { ListView } from '@/react/edge/edge-jobs/ListView';
+import { CreateView } from '@/react/edge/edge-jobs/CreateView/CreateView';
export const jobsModule = angular
.module('portainer.edge.react.views.jobs', [])
+ .component('edgeJobsView', r2a(withUIRouter(withCurrentUser(ListView)), []))
.component(
- 'edgeJobsView',
- r2a(withUIRouter(withCurrentUser(ListView)), [])
+ 'edgeJobsCreateView',
+ r2a(withUIRouter(withCurrentUser(CreateView)), [])
).name;
diff --git a/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobView.html b/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobView.html
deleted file mode 100644
index ee1430ec1..000000000
--- a/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobView.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
diff --git a/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobViewController.js b/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobViewController.js
deleted file mode 100644
index 97baf8ea8..000000000
--- a/app/edge/views/edge-jobs/createEdgeJobView/createEdgeJobViewController.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { confirmWebEditorDiscard } from '@@/modals/confirm';
-
-export class CreateEdgeJobViewController {
- /* @ngInject */
- constructor($async, $q, $state, $window, EdgeJobService, GroupService, Notifications, TagService) {
- this.state = {
- actionInProgress: false,
- isEditorDirty: false,
- };
-
- this.model = {
- Name: '',
- Recurring: false,
- CronExpression: '',
- Endpoints: [],
- FileContent: '',
- File: null,
- EdgeGroups: [],
- };
-
- this.$async = $async;
- this.$q = $q;
- this.$state = $state;
- this.$window = $window;
- this.Notifications = Notifications;
- this.GroupService = GroupService;
- this.EdgeJobService = EdgeJobService;
- this.TagService = TagService;
-
- this.create = this.create.bind(this);
- this.createEdgeJob = this.createEdgeJob.bind(this);
- this.createAsync = this.createAsync.bind(this);
- }
-
- create(method) {
- return this.$async(this.createAsync, method);
- }
-
- async createAsync(method) {
- this.state.actionInProgress = true;
-
- try {
- await this.createEdgeJob(method, this.model);
- this.Notifications.success('Success', 'Edge job successfully created');
- this.state.isEditorDirty = false;
- this.$state.go('edge.jobs', {}, { reload: true });
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to create Edge job');
- }
-
- this.state.actionInProgress = false;
- }
-
- createEdgeJob(method, model) {
- if (method === 'editor') {
- return this.EdgeJobService.createEdgeJobFromFileContent(model);
- }
- return this.EdgeJobService.createEdgeJobFromFileUpload(model);
- }
-
- async uiCanExit() {
- if (this.model.FileContent && this.state.isEditorDirty) {
- return confirmWebEditorDiscard();
- }
- }
-
- async $onInit() {
- try {
- const [groups, tags] = await Promise.all([this.GroupService.groups(), this.TagService.tags()]);
- this.groups = groups;
- this.tags = tags;
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to retrieve page data');
- }
-
- this.$window.onbeforeunload = () => {
- if (this.model.FileContent && this.state.isEditorDirty) {
- return '';
- }
- };
- }
-
- $onDestroy() {
- this.state.isEditorDirty = false;
- }
-}
diff --git a/app/edge/views/edge-jobs/createEdgeJobView/index.js b/app/edge/views/edge-jobs/createEdgeJobView/index.js
deleted file mode 100644
index ecddfb5ae..000000000
--- a/app/edge/views/edge-jobs/createEdgeJobView/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import angular from 'angular';
-import { CreateEdgeJobViewController } from './createEdgeJobViewController';
-
-angular.module('portainer.edge').component('createEdgeJobView', {
- templateUrl: './createEdgeJobView.html',
- controller: CreateEdgeJobViewController,
-});
diff --git a/app/react/components/DateTimeField.tsx b/app/react/components/DateTimeField.tsx
new file mode 100644
index 000000000..d697f791e
--- /dev/null
+++ b/app/react/components/DateTimeField.tsx
@@ -0,0 +1,57 @@
+import DateTimePicker from 'react-datetime-picker';
+import { Calendar, X } from 'lucide-react';
+
+import { isoDate } from '@/portainer/filters/filters';
+import { AutomationTestingProps } from '@/types';
+
+import { FormControl } from '@@/form-components/FormControl';
+import { Input } from '@@/form-components/Input';
+
+import 'react-datetime-picker/dist/DateTimePicker.css';
+import 'react-calendar/dist/Calendar.css';
+
+export const FORMAT = 'YYYY-MM-DD HH:mm';
+
+export function DateTimeField({
+ error,
+ label,
+ disabled,
+ name,
+ value,
+ onChange,
+ minDate,
+ 'data-cy': dataCy,
+}: {
+ error?: string;
+ disabled?: boolean;
+ name: string;
+ value: Date | null;
+ onChange: (date: Date | null) => void;
+ label: string;
+ minDate?: Date;
+} & AutomationTestingProps) {
+ return (
+
+ {!disabled ? (
+ }
+ clearIcon={}
+ disableClock
+ data-cy={dataCy}
+ minDate={minDate}
+ />
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/app/react/components/WebEditorForm.tsx b/app/react/components/WebEditorForm.tsx
index f8b150e11..36f05969e 100644
--- a/app/react/components/WebEditorForm.tsx
+++ b/app/react/components/WebEditorForm.tsx
@@ -59,6 +59,7 @@ interface Props extends AutomationTestingProps {
id: string;
placeholder?: string;
yaml?: boolean;
+ shell?: boolean;
readonly?: boolean;
titleContent?: React.ReactNode;
hideTitle?: boolean;
@@ -77,6 +78,7 @@ export function WebEditorForm({
hideTitle,
readonly,
yaml,
+ shell,
children,
error,
versions,
@@ -108,6 +110,7 @@ export function WebEditorForm({
placeholder={placeholder}
readonly={readonly}
yaml={yaml}
+ shell={shell}
value={value}
onChange={onChange}
versions={versions}
diff --git a/app/react/components/form-components/Input/Select.tsx b/app/react/components/form-components/Input/Select.tsx
index 561de3cd2..8c9d11b7b 100644
--- a/app/react/components/form-components/Input/Select.tsx
+++ b/app/react/components/form-components/Input/Select.tsx
@@ -11,7 +11,7 @@ export interface Option
}
interface Props extends AutomationTestingProps {
- options: Option[];
+ options: Array