diff --git a/src/pages/compute/containers/Instance/actions/Rescue.jsx b/src/pages/compute/containers/Instance/actions/Rescue.jsx new file mode 100644 index 00000000..ca4af7be --- /dev/null +++ b/src/pages/compute/containers/Instance/actions/Rescue.jsx @@ -0,0 +1,77 @@ +import { inject, observer } from 'mobx-react'; +import globalServerStore from 'stores/nova/instance'; +import { ModalAction } from 'containers/Action'; +import { getPasswordOtherRule } from 'utils/validate'; +import globalImageStore from 'src/stores/glance/image'; +import { checkStatus, isIronicInstance, isNotLockedOrAdmin } from 'src/resources/nova/instance'; + +export class Rescue extends ModalAction { + static id = 'rescue'; + + static title = t('Rescue'); + + init() { + this.store = globalServerStore; + this.imageStore = globalImageStore; + this.fetchImages(); + } + + get name() { + return t('Rescue Instance'); + } + + get tips() { + return t( + 'The rescue mode is only for emergency purpose, for example in case of a system or access failure. This will shut down your instance and mount the root disk to a temporary server. Then, you will be able to connect to this server, repair the system configuration or recover your data.You may optionally select an image and set a password on the rescue instance server.' + ); + } + + static isUnrescue = (item) => checkStatus(['rescue'], item); + + static allowed = (item, containerProps) => { + const { isAdminPage } = containerProps; + return Promise.resolve( + !this.isUnrescue(item) && + isNotLockedOrAdmin(item, isAdminPage) && + !isIronicInstance(item) + ); + }; + + async fetchImages() { + await this.imageStore.fetchList({ all_projects: this.hasAdminRole }); + } + + get getData() { + return (this.imageStore.list.data).map((it) => ({ value: it.id, label: it.name })) || []; + } + + get formItems() { + return [ + { + name: 'rescue_image_ref', + label: t('Select Image'), + type: 'select', + options: this.getData, + }, + { + name: 'adminPass', + label: t('Password'), + type: 'input-password', + otherRule: getPasswordOtherRule('password', 'instance'), + }, + ]; + } + + onSubmit = (values) => { + const { id } = this.item; + const rescue = { + rescue: { + adminPass: values.adminPass, + rescue_image_ref: values.rescue_image_ref + } + } + return this.store.rescue(id, rescue); + }; +} + +export default inject('rootStore')(observer(Rescue)); \ No newline at end of file diff --git a/src/pages/compute/containers/Instance/actions/index.jsx b/src/pages/compute/containers/Instance/actions/index.jsx index 5f37760a..a2de4cd7 100644 --- a/src/pages/compute/containers/Instance/actions/index.jsx +++ b/src/pages/compute/containers/Instance/actions/index.jsx @@ -48,6 +48,7 @@ import DeleteIronic from './DeleteIronic'; import ConfirmResize from './ConfirmResize'; import RevertResize from './RevertResize'; import ModifyTags from './ModifyTags'; +import Rescue from './Rescue'; const statusActions = [ StartAction, @@ -62,6 +63,7 @@ const statusActions = [ UnpauseAction, Shelve, Unshelve, + Rescue, ]; const resourceActions = [ diff --git a/src/stores/nova/instance.js b/src/stores/nova/instance.js index 797df048..286d6b8d 100644 --- a/src/stores/nova/instance.js +++ b/src/stores/nova/instance.js @@ -238,7 +238,7 @@ export class ServerStore extends Base { sgItems = result.map((it) => this.mapperSecurityGroupRule(it.security_group) ); - } catch (e) {} + } catch (e) { } this.securityGroups = { data: sgItems || [], interfaces: ports, @@ -351,6 +351,11 @@ export class ServerStore extends Base { return this.client.action(id, body); } + @action + async rescue(id, rescue) { + return this.client.action(id, rescue); + } + @action async softReboot({ id }) { const body = {