1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-06 06:15:22 +02:00

feat(app): Prevent web editor related views from being accidentally closed (#4715)

* feat(app): when leaving a view with unsaved changed, a modal prompt the user with a confirmation message

feat(app): when leaving a view with unsaved changes, a modal prompt the user with a confirmation message

* feat(app/web-editor): fix the modal behaviour when editing a stack details

* feat(app/web-editor): add a reusable function confirmWebEditorDiscard in modal service

* feat(docker/stack): fix missing dependency
This commit is contained in:
Alice Groux 2021-03-20 22:13:27 +01:00 committed by GitHub
parent d0d38990c7
commit a7ed6222b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 271 additions and 15 deletions

View file

@ -14,6 +14,7 @@
form-action="$ctrl.create"
form-action-label="Create edge job"
action-in-progress="$ctrl.state.actionInProgress"
is-editor-dirty="$ctrl.state.isEditorDirty"
></edge-job-form>
</rd-widget-body>
</rd-widget>

View file

@ -1,8 +1,9 @@
export class CreateEdgeJobViewController {
/* @ngInject */
constructor($async, $q, $state, EdgeJobService, GroupService, Notifications, TagService) {
constructor($async, $q, $state, $window, ModalService, EdgeJobService, GroupService, Notifications, TagService) {
this.state = {
actionInProgress: false,
isEditorDirty: false,
};
this.model = {
@ -17,6 +18,8 @@ export class CreateEdgeJobViewController {
this.$async = $async;
this.$q = $q;
this.$state = $state;
this.$window = $window;
this.ModalService = ModalService;
this.Notifications = Notifications;
this.GroupService = GroupService;
this.EdgeJobService = EdgeJobService;
@ -37,6 +40,7 @@ export class CreateEdgeJobViewController {
try {
await this.createEdgeJob(method, this.model);
this.Notifications.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');
@ -52,6 +56,12 @@ export class CreateEdgeJobViewController {
return this.EdgeJobService.createEdgeJobFromFileUpload(model);
}
async uiCanExit() {
if (this.model.FileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
}
}
async $onInit() {
try {
const [groups, tags] = await Promise.all([this.GroupService.groups(), this.TagService.tags()]);
@ -60,5 +70,11 @@ export class CreateEdgeJobViewController {
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve page data');
}
this.$window.onbeforeunload = () => {
if (this.model.FileContent && this.state.isEditorDirty) {
return '';
}
};
}
}

View file

@ -24,6 +24,7 @@
form-action="$ctrl.update"
form-action-label="Update Edge job"
action-in-progress="$ctrl.state.actionInProgress"
is-editor-dirty="$ctrl.state.isEditorDirty"
></edge-job-form>
</uib-tab>

View file

@ -2,15 +2,18 @@ import _ from 'lodash-es';
export class EdgeJobController {
/* @ngInject */
constructor($async, $q, $state, EdgeJobService, EndpointService, FileSaver, GroupService, HostBrowserService, Notifications, TagService) {
constructor($async, $q, $state, $window, ModalService, EdgeJobService, EndpointService, FileSaver, GroupService, HostBrowserService, Notifications, TagService) {
this.state = {
actionInProgress: false,
showEditorTab: false,
isEditorDirty: false,
};
this.$async = $async;
this.$q = $q;
this.$state = $state;
this.$window = $window;
this.ModalService = ModalService;
this.EdgeJobService = EdgeJobService;
this.EndpointService = EndpointService;
this.FileSaver = FileSaver;
@ -43,6 +46,7 @@ export class EdgeJobController {
try {
await this.EdgeJobService.updateEdgeJob(model);
this.Notifications.success('Edge job successfully updated');
this.state.isEditorDirty = false;
this.$state.go('edge.jobs', {}, { reload: true });
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to update Edge job');
@ -121,6 +125,12 @@ export class EdgeJobController {
this.state.showEditorTab = true;
}
async uiCanExit() {
if (this.edgeJob.FileContent !== this.oldFileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
}
}
async $onInit() {
const { id, tab } = this.$state.params;
this.state.activeTab = tab;
@ -138,6 +148,7 @@ export class EdgeJobController {
]);
edgeJob.FileContent = file.FileContent;
this.oldFileContent = edgeJob.FileContent;
this.edgeJob = edgeJob;
this.groups = groups;
this.tags = tags;
@ -152,5 +163,11 @@ export class EdgeJobController {
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve endpoint list');
}
this.$window.onbeforeunload = () => {
if (this.edgeJob.FileContent !== this.oldFileContent && this.state.isEditorDirty) {
return '';
}
};
}
}

View file

@ -2,8 +2,8 @@ import _ from 'lodash-es';
export class CreateEdgeStackViewController {
/* @ngInject */
constructor($state, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async) {
Object.assign(this, { $state, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async });
constructor($state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async) {
Object.assign(this, { $state, $window, ModalService, EdgeStackService, EdgeGroupService, EdgeTemplateService, Notifications, FormHelper, $async });
this.formValues = {
Name: '',
@ -24,6 +24,7 @@ export class CreateEdgeStackViewController {
formValidationError: '',
actionInProgress: false,
StackType: null,
isEditorDirty: false,
};
this.edgeGroups = null;
@ -41,6 +42,12 @@ export class CreateEdgeStackViewController {
this.onChangeMethod = this.onChangeMethod.bind(this);
}
async uiCanExit() {
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
}
}
async $onInit() {
try {
this.edgeGroups = await this.EdgeGroupService.groups();
@ -55,6 +62,12 @@ export class CreateEdgeStackViewController {
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve Templates');
}
this.$window.onbeforeunload = () => {
if (this.state.Method === 'editor' && this.formValues.StackFileContent && this.state.isEditorDirty) {
return '';
}
};
}
createStack() {
@ -97,6 +110,7 @@ export class CreateEdgeStackViewController {
await this.createStackByMethod(name, method);
this.Notifications.success('Stack successfully deployed');
this.state.isEditorDirty = false;
this.$state.go('edge.stacks');
} catch (err) {
this.Notifications.error('Deployment error', err, 'Unable to deploy stack');
@ -149,5 +163,6 @@ export class CreateEdgeStackViewController {
editorUpdate(cm) {
this.formValues.StackFileContent = cm.getValue();
this.state.isEditorDirty = true;
}
}

View file

@ -21,6 +21,7 @@
model="$ctrl.formValues"
action-in-progress="$ctrl.state.actionInProgress"
submit-action="$ctrl.deployStack"
is-editor-dirty="$ctrl.state.isEditorDirty"
></edit-edge-stack-form>
</div>
</uib-tab>

View file

@ -2,9 +2,11 @@ import _ from 'lodash-es';
export class EditEdgeStackViewController {
/* @ngInject */
constructor($async, $state, EdgeGroupService, EdgeStackService, EndpointService, Notifications) {
constructor($async, $state, $window, ModalService, EdgeGroupService, EdgeStackService, EndpointService, Notifications) {
this.$async = $async;
this.$state = $state;
this.$window = $window;
this.ModalService = ModalService;
this.EdgeGroupService = EdgeGroupService;
this.EdgeStackService = EdgeStackService;
this.EndpointService = EndpointService;
@ -16,6 +18,7 @@ export class EditEdgeStackViewController {
this.state = {
actionInProgress: false,
activeTab: 0,
isEditorDirty: false,
};
this.deployStack = this.deployStack.bind(this);
@ -38,9 +41,22 @@ export class EditEdgeStackViewController {
EdgeGroups: this.stack.EdgeGroups,
Prune: this.stack.Prune,
};
this.oldFileContent = this.formValues.StackFileContent;
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve stack data');
}
this.$window.onbeforeunload = () => {
if (this.formValues.StackFileContent !== this.oldFileContent && this.state.isEditorDirty) {
return '';
}
};
}
async uiCanExit() {
if (this.formValues.StackFileContent !== this.oldFileContent && this.state.isEditorDirty) {
return this.ModalService.confirmWebEditorDiscard();
}
}
filterStackEndpoints(groupIds, groups) {
@ -64,6 +80,7 @@ export class EditEdgeStackViewController {
}
await this.EdgeStackService.updateStack(this.stack.Id, this.formValues);
this.Notifications.success('Stack successfully deployed');
this.state.isEditorDirty = false;
this.$state.go('edge.stacks');
} catch (err) {
this.Notifications.error('Deployment error', err, 'Unable to deploy stack');