diff --git a/src/components/ReactCron/ReactCron.js b/src/components/ReactCron/ReactCron.js index e8f250e8..08b21f96 100644 --- a/src/components/ReactCron/ReactCron.js +++ b/src/components/ReactCron/ReactCron.js @@ -26,7 +26,7 @@ const steps = [ title: 'Month', content: 'month', }, -]; +] class ReactCron extends React.Component { constructor(props) { @@ -38,49 +38,49 @@ class ReactCron extends React.Component { cornFormat: false, current: 0, activeKey: 'Cron', - second:{ + second: { cronEvery: '1', incrementStart: 3, incrementIncrement: 5, rangeStart: 1, rangeEnd: 0, - specificSpecific:[], + specificSpecific: [], }, minutesText: '*', minutesValue: null, - minutes:{ + minutes: { cronEvery: '1', incrementStart: 3, incrementIncrement: 5, rangeStart: 0, rangeEnd: 0, - specificSpecific:[], + specificSpecific: [], }, hourText: '*', hourValue: null, - hour:{ + hour: { cronEvery: '1', incrementStart: 3, incrementIncrement: 5, rangeStart: 0, rangeEnd: 0, - specificSpecific:[], + specificSpecific: [], }, dayText: '*', dayValue: null, - day:{ + day: { cronEvery: '1', incrementStart: 1, incrementIncrement: 1, rangeStart: 1, rangeEnd: 0, - specificSpecific:[], + specificSpecific: [], cronLastSpecificDomDay: '1L', cronDaysBeforeEomMinus: 1, cronDaysNearestWeekday: 1, }, weekText: '?', - week:{ + week: { cronEvery: '', incrementStart: '1', incrementIncrement: '1', @@ -90,25 +90,26 @@ class ReactCron extends React.Component { }, monthText: '*', monthValue: null, - month:{ + month: { cronEvery: '1', incrementStart: 3, incrementIncrement: 5, rangeStart: 1, rangeEnd: 1, - specificSpecific:[], + specificSpecific: [], }, yearText: '*', - year:{ + year: { cronEvery: '1', incrementStart: 1, incrementIncrement: 2019, rangeStart: 2019, rangeEnd: 2019, - specificSpecific:[], + specificSpecific: [], }, } } + componentDidMount() { let cornText = this.props.cron ? this.props.cron : `${this.state.minutesText} ${this.state.hourText} ${this.state.dayText} ${this.state.monthText} ${this.state.weekText}` let prettyCronText = prettyCron.toString(cornText) @@ -117,7 +118,7 @@ class ReactCron extends React.Component { cornText, prettyCronText, }, () => { - if( !cronValidate(cornText) ) { + if (!cronValidate(cornText)) { this.setState({ ...this.state, cornFormat: true, @@ -141,7 +142,7 @@ class ReactCron extends React.Component { cornText, prettyCronText, }, () => { - if( !cronValidate(cornText) ) { + if (!cronValidate(cornText)) { this.setState({ ...this.state, cornFormat: true, @@ -156,12 +157,13 @@ class ReactCron extends React.Component { } }) } - /*second*/ + + /* second */ secondOnChange = (val) => { let seconds = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.second, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.second, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -171,7 +173,7 @@ class ReactCron extends React.Component { this.prettyCronfun() break case '2': - seconds = this.state.second.incrementStart+'/'+this.state.second.incrementIncrement + seconds = `${this.state.second.incrementStart}/${this.state.second.incrementIncrement}` this.setState({ ...this.state, secondText: seconds, @@ -180,10 +182,10 @@ class ReactCron extends React.Component { this.prettyCronfun() break case '3': - if(this.state.second.specificSpecific.length > 0){ + if (this.state.second.specificSpecific.length > 0) { seconds = '' - this.state.second.specificSpecific.forEach(val=> { - seconds += val+',' + this.state.second.specificSpecific.forEach(val => { + seconds += `${val},` }) seconds = seconds.slice(0, -1) } @@ -195,7 +197,7 @@ class ReactCron extends React.Component { this.prettyCronfun() break case '4': - seconds = this.state.second.rangeStart+'-'+this.state.second.rangeEnd + seconds = `${this.state.second.rangeStart}-${this.state.second.rangeEnd}` this.setState({ ...this.state, secondText: seconds, @@ -203,30 +205,30 @@ class ReactCron extends React.Component { }) this.prettyCronfun() break - default : + default: break } } - secondMultipleChange = (valArr)=> { + secondMultipleChange = (valArr) => { let seconds = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { seconds = '' valArr.forEach(val => { - seconds += val+',' + seconds += `${val},` }) seconds = seconds.slice(0, -1) } - let data = Object.assign({}, this.state.second, { specificSpecific: valArr, secondText: this.state.second.cronEvery === '3' ? seconds : this.state.secondText }) - this.setState({...this.state, secondText: data.secondText, second: data }) + let data = Object.assign({}, this.state.second, { specificSpecific: valArr, secondText: this.state.second.cronEvery === '3' ? seconds : this.state.secondText }) + this.setState({ ...this.state, secondText: data.secondText, second: data }) } - /*minutes*/ + /* minutes */ minutesOnChange =(val) => { let minutes = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.minutes, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.minutes, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -238,12 +240,12 @@ class ReactCron extends React.Component { dayText: '*', weekText: '?', minutes: data, - },() => { + }, () => { this.prettyCronfun() }) break case '2': - minutes = this.state.minutes.incrementIncrement + '/' + this.state.minutes.incrementStart + minutes = `${this.state.minutes.incrementIncrement}/${this.state.minutes.incrementStart}` this.setState({ ...this.state, minutesText: minutes, @@ -254,10 +256,10 @@ class ReactCron extends React.Component { }) break case '3': - if(this.state.minutes.specificSpecific.length > 0){ + if (this.state.minutes.specificSpecific.length > 0) { minutes = '' - this.state.minutes.specificSpecific.forEach(val=> { - minutes += val+',' + this.state.minutes.specificSpecific.forEach(val => { + minutes += `${val},` }) minutes = minutes.slice(0, -1) } @@ -271,7 +273,7 @@ class ReactCron extends React.Component { }) break case '4': - minutes = this.state.minutes.rangeStart + '-' + this.state.minutes.rangeEnd + minutes = `${this.state.minutes.rangeStart}-${this.state.minutes.rangeEnd}` this.setState({ ...this.state, minutesText: minutes, @@ -281,30 +283,30 @@ class ReactCron extends React.Component { this.prettyCronfun() }) break - default : + default: break } } - minutesMultipleChange = (valArr)=> { + minutesMultipleChange = (valArr) => { let minutes = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { minutes = '' - valArr.forEach(val=> { - minutes += val+',' + valArr.forEach(val => { + minutes += `${val},` }) minutes = minutes.slice(0, -1) } - let data = Object.assign({}, this.state.minutes, { specificSpecific: valArr, minutesText: this.state.minutes.cronEvery === '3' ? minutes : this.state.minutesText }) - this.setState({...this.state, minutesText: data.minutesText, minutes: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.minutes, { specificSpecific: valArr, minutesText: this.state.minutes.cronEvery === '3' ? minutes : this.state.minutesText }) + this.setState({ ...this.state, minutesText: data.minutesText, minutes: data }, () => { this.prettyCronfun() }) } - /*hour*/ + /* hour */ hourOnChange =(val) => { let hour = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.hour, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.hour, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -314,12 +316,12 @@ class ReactCron extends React.Component { minutesValue: this.state.minutesText === '*' ? null : this.state.minutesValue, dayValue: null, hour: data, - },() => { + }, () => { this.prettyCronfun() }) break case '2': - hour = this.state.hour.incrementIncrement + '/' + this.state.hour.incrementStart + hour = `${this.state.hour.incrementIncrement}/${this.state.hour.incrementStart}` this.setState({ ...this.state, hourText: hour, @@ -327,15 +329,15 @@ class ReactCron extends React.Component { minutesText: this.state.minutesText === '*' ? '0' : this.state.minutesText, minutesValue: this.state.minutesText === '*' ? null : this.state.minutesValue, hour: data, - },() => { + }, () => { this.prettyCronfun() }) break case '3': - if(this.state.hour.specificSpecific.length > 0){ + if (this.state.hour.specificSpecific.length > 0) { hour = '' - this.state.hour.specificSpecific.forEach(val=> { - hour += val+',' + this.state.hour.specificSpecific.forEach(val => { + hour += `${val},` }) hour = hour.slice(0, -1) } @@ -346,12 +348,12 @@ class ReactCron extends React.Component { minutesText: this.state.minutesText === '*' ? '0' : this.state.minutesText, minutesValue: this.state.minutesText === '*' ? null : this.state.minutesValue, hour: data, - },() => { + }, () => { this.prettyCronfun() }) break case '4': - hour = this.state.hour.rangeStart + '-' + this.state.hour.rangeEnd + hour = `${this.state.hour.rangeStart}-${this.state.hour.rangeEnd}` this.setState({ ...this.state, hourText: hour, @@ -359,35 +361,35 @@ class ReactCron extends React.Component { minutesText: this.state.minutesText === '*' ? '0' : this.state.minutesText, minutesValue: this.state.minutesText === '*' ? null : this.state.minutesValue, hour: data, - },() => { + }, () => { this.prettyCronfun() }) break - default : + default: break } } - hourMultipleChange = (valArr)=> { + hourMultipleChange = (valArr) => { let hour = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { hour = '' - valArr.forEach(val=> { - hour += val+',' + valArr.forEach(val => { + hour += `${val},` }) hour = hour.slice(0, -1) } - let data = Object.assign({}, this.state.hour, { specificSpecific: valArr, hourText: this.state.hour.cronEvery === '3' ? hour : this.state.hourText }) - this.setState({...this.state, hourText: data.hourText, hour: data },() => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.hour, { specificSpecific: valArr, hourText: this.state.hour.cronEvery === '3' ? hour : this.state.hourText }) + this.setState({ ...this.state, hourText: data.hourText, hour: data }, () => { this.prettyCronfun() }) } - /*day*/ + /* day */ dayOnChange = (val) => { let weeks = '?' let day = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.day, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.day, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -400,7 +402,7 @@ class ReactCron extends React.Component { dayValue: cronEvery, monthValue: null, weekText: '?', - },() => { + }, () => { this.prettyCronfun() }) break @@ -415,7 +417,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, weekText: `${this.state.week.incrementIncrement}/${this.state.week.incrementStart}`, dayValue: cronEvery, - },() => { + }, () => { this.prettyCronfun() }) break @@ -430,15 +432,15 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayText: `${this.state.day.incrementIncrement}/${this.state.day.incrementStart}`, dayValue: cronEvery, - },() => { + }, () => { this.prettyCronfun() }) break case '4': - if(this.state.week.specificSpecific.length > 0){ + if (this.state.week.specificSpecific.length > 0) { weeks = '' - this.state.week.specificSpecific.forEach(val=> { - weeks += val+',' + this.state.week.specificSpecific.forEach(val => { + weeks += `${val},` }) weeks = weeks.slice(0, -1) } @@ -452,15 +454,15 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: '?', - },() => { + }, () => { this.prettyCronfun() }) break case '5': - if(this.state.day.specificSpecific.length > 0){ + if (this.state.day.specificSpecific.length > 0) { day = '' - this.state.day.specificSpecific.forEach(val=> { - day += val+',' + this.state.day.specificSpecific.forEach(val => { + day += `${val},` }) day = day.slice(0, -1) } @@ -474,7 +476,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: day, - },() => { + }, () => { this.prettyCronfun() }) break @@ -489,7 +491,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: 'L', - },() => { + }, () => { this.prettyCronfun() }) break @@ -504,7 +506,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: 'LW', - },() => { + }, () => { this.prettyCronfun() }) break @@ -519,7 +521,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: this.state.day.cronLastSpecificDomDay, - },() => { + }, () => { this.prettyCronfun() }) break @@ -534,7 +536,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: `L-${this.state.day.cronDaysBeforeEomMinus}`, - },() => { + }, () => { this.prettyCronfun() }) break @@ -549,7 +551,7 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, dayValue: cronEvery, dayText: `${this.state.day.cronDaysBeforeEomMinus}W`, - },() => { + }, () => { this.prettyCronfun() }) break @@ -564,64 +566,64 @@ class ReactCron extends React.Component { hourValue: this.state.hourText === '*' ? null : this.state.hourValue, weekText: `${this.state.week.cronNthDayDay}#${this.state.week.cronNthDayNth}`, dayValue: cronEvery, - },() => { + }, () => { this.prettyCronfun() }) break - default : + default: break } } weekChange = (val) => { let weeks = '?' - weeks = this.state.week.incrementIncrement+'/'+val - let data = Object.assign({}, this.state.week, { weekText: this.state.day.cronEvery === '2' ? weeks : this.state.weekText, incrementStart: val }) - this.setState({...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) + weeks = `${this.state.week.incrementIncrement}/${val}` + let data = Object.assign({}, this.state.week, { weekText: this.state.day.cronEvery === '2' ? weeks : this.state.weekText, incrementStart: val }) + this.setState({ ...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) } weekSpeChange = (valArr) => { let weeks = '?' - if(valArr.length > 0){ + if (valArr.length > 0) { weeks = '' - valArr.forEach(val=> { - weeks += val+',' + valArr.forEach(val => { + weeks += `${val},` }) weeks = weeks.slice(0, -1) } - let data = Object.assign({}, this.state.week, { specificSpecific: valArr, weekText: this.state.day.cronEvery === '4' ? weeks : this.state.weekText }) - this.setState({...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.week, { specificSpecific: valArr, weekText: this.state.day.cronEvery === '4' ? weeks : this.state.weekText }) + this.setState({ ...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) } daySpeChange = (valArr) => { let day = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { day = '' - valArr.forEach(val=> { - day += val+',' + valArr.forEach(val => { + day += `${val},` }) day = day.slice(0, -1) } - let data = Object.assign({}, this.state.day, { specificSpecific: valArr, dayText: this.state.day.cronEvery === '5' ? day : this.state.dayText }) - this.setState({...this.state, dayText: data.dayText, day: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.day, { specificSpecific: valArr, dayText: this.state.day.cronEvery === '5' ? day : this.state.dayText }) + this.setState({ ...this.state, dayText: data.dayText, day: data }, () => { this.prettyCronfun() }) } dayLaChange = (val) => { - let data = Object.assign({}, this.state.day, { dayText: this.state.day.cronEvery === '8' ? val : this.state.dayText, cronLastSpecificDomDay: val }) - this.setState({...this.state, dayText: data.dayText, day: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.day, { dayText: this.state.day.cronEvery === '8' ? val : this.state.dayText, cronLastSpecificDomDay: val }) + this.setState({ ...this.state, dayText: data.dayText, day: data }, () => { this.prettyCronfun() }) } weekLaChange = (val) => { - let data = Object.assign({}, this.state.week, { weekText: this.state.day.cronEvery === '11' ? `${val}#${this.state.week.cronNthDayNth}` : this.state.weekText, cronNthDayDay: val }) - this.setState({...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.week, { weekText: this.state.day.cronEvery === '11' ? `${val}#${this.state.week.cronNthDayNth}` : this.state.weekText, cronNthDayDay: val }) + this.setState({ ...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) } - /*month*/ + /* month */ monthOnChange = (val) => { let month = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.month, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.month, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -639,7 +641,7 @@ class ReactCron extends React.Component { }) break case '2': - month = this.state.month.incrementIncrement + '/' + this.state.month.incrementStart + month = `${this.state.month.incrementIncrement}/${this.state.month.incrementStart}` this.setState({ ...this.state, monthText: month, @@ -656,10 +658,10 @@ class ReactCron extends React.Component { }) break case '3': - if(this.state.month.specificSpecific.length > 0){ + if (this.state.month.specificSpecific.length > 0) { month = '' - this.state.month.specificSpecific.forEach(val=> { - month += val+',' + this.state.month.specificSpecific.forEach(val => { + month += `${val},` }) month = month.slice(0, -1) } @@ -679,7 +681,7 @@ class ReactCron extends React.Component { }) break case '4': - month = this.state.month.rangeStart + '-' + this.state.month.rangeEnd + month = `${this.state.month.rangeStart}-${this.state.month.rangeEnd}` this.setState({ ...this.state, monthText: month, @@ -695,30 +697,30 @@ class ReactCron extends React.Component { this.prettyCronfun() }) break - default : + default: break } } monthMultipleChange = (valArr) => { let month = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { month = '' - valArr.forEach(val=> { - month += val+',' + valArr.forEach(val => { + month += `${val},` }) month = month.slice(0, -1) } - let data = Object.assign({}, this.state.month, { specificSpecific: valArr, monthText: this.state.month.cronEvery === '3' ? month : this.state.monthText }) - this.setState({...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() }) + let data = Object.assign({}, this.state.month, { specificSpecific: valArr, monthText: this.state.month.cronEvery === '3' ? month : this.state.monthText }) + this.setState({ ...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() }) } - /*year*/ + /* year */ yearOnChange = (val) => { let year = '*' - let cronEvery= val.target ? val.target.value : val - let data = Object.assign({}, this.state.year, {cronEvery: cronEvery.toString()}) - switch (cronEvery.toString()){ + let cronEvery = val.target ? val.target.value : val + let data = Object.assign({}, this.state.year, { cronEvery: cronEvery.toString() }) + switch (cronEvery.toString()) { case '1': this.setState({ ...this.state, @@ -727,7 +729,7 @@ class ReactCron extends React.Component { }) break case '2': - year = this.state.year.incrementStart + '/' + this.state.year.incrementIncrement + year = `${this.state.year.incrementStart}/${this.state.year.incrementIncrement}` this.setState({ ...this.state, yearText: year, @@ -735,10 +737,10 @@ class ReactCron extends React.Component { }) break case '3': - if(this.state.year.specificSpecific.length > 0){ + if (this.state.year.specificSpecific.length > 0) { year = '' - this.state.year.specificSpecific.forEach(val=> { - year += val+',' + this.state.year.specificSpecific.forEach(val => { + year += `${val},` }) year = year.slice(0, -1) } @@ -749,40 +751,40 @@ class ReactCron extends React.Component { }) break case '4': - year = this.state.year.rangeStart + '-' + this.state.year.rangeEnd + year = `${this.state.year.rangeStart}-${this.state.year.rangeEnd}` this.setState({ ...this.state, yearText: year, year: data, }) break - default : + default: break } } yearMultipleChange = (valArr) => { let year = '*' - if(valArr.length > 0){ + if (valArr.length > 0) { year = '' - valArr.forEach(val=> { - year += val+',' + valArr.forEach(val => { + year += `${val},` }) year = year.slice(0, -1) } - let data = Object.assign({}, this.state.year, { specificSpecific: valArr, yearText: this.state.year.cronEvery === '3' ? year : this.state.yearText }) - this.setState({...this.state, yearText: data.yearText, year: data }) + let data = Object.assign({}, this.state.year, { specificSpecific: valArr, yearText: this.state.year.cronEvery === '3' ? year : this.state.yearText }) + this.setState({ ...this.state, yearText: data.yearText, year: data }) } CronInputChange = (e) => { e.persist() - if(e.target && e.target.value !== 'undefined') { + if (e.target && e.target.value !== 'undefined') { this.setState({ ...this.state, cornText: e.target.value, prettyCronText: prettyCron.toString(e.target.value), }, () => { - if( !cronValidate(e.target.value) ) { + if (!cronValidate(e.target.value)) { this.setState({ ...this.state, cornFormat: true, @@ -800,17 +802,17 @@ class ReactCron extends React.Component { } next() { - const current = this.state.current + 1; - this.setState({ current }); + const current = this.state.current + 1 + this.setState({ current }) } prev() { - const current = this.state.current - 1; - this.setState({ current }); + const current = this.state.current - 1 + this.setState({ current }) } activeKeyChange(key) { - this.setState({activeKey: key}) + this.setState({ activeKey: key }) } render() { @@ -823,13 +825,13 @@ class ReactCron extends React.Component { for (let i = 0; i < 24; i++) { childrenHour.push() } - const weekAry = [{ val: 1, label:'Sunday' }, { val: 2, label:'Monday' }, { val: 3, label:'Tuesday' }, { val: 4, label:'Wednesday' }, { val: 5, label:'Thursday' }, { val: 6, label:'Friday' }, { val: 7, label:'Saturday' }] + const weekAry = [{ val: 1, label: 'Sunday' }, { val: 2, label: 'Monday' }, { val: 3, label: 'Tuesday' }, { val: 4, label: 'Wednesday' }, { val: 5, label: 'Thursday' }, { val: 6, label: 'Friday' }, { val: 7, label: 'Saturday' }] const childrenWeekAry = [] for (let i = 0; i < 7; i++) { childrenWeekAry.push() } - const weekSpeAry = [{ val: 'SUN', label:'Sunday' }, { val: 'MON', label:'Monday' }, { val: 'TUE', label:'Tuesday' }, { val: 'WED', label:'Wednesday' }, { val: 'THU', label:'Thursday' }, { val: 'FRI', label:'Friday' }, { val: 'SAT', label:'Saturday' }] + const weekSpeAry = [{ val: 'SUN', label: 'Sunday' }, { val: 'MON', label: 'Monday' }, { val: 'TUE', label: 'Tuesday' }, { val: 'WED', label: 'Wednesday' }, { val: 'THU', label: 'Thursday' }, { val: 'FRI', label: 'Friday' }, { val: 'SAT', label: 'Saturday' }] const childrenSpeWeekAry = [] for (let i = 0; i < 7; i++) { childrenSpeWeekAry.push() @@ -840,7 +842,7 @@ class ReactCron extends React.Component { childrenSpeMonthAry.push() } - const weekLAry = [{ val: '1L', label:'Sunday' }, { val: '2L', label:'Monday' }, { val: '3L', label:'Tuesday' }, { val: '4L', label:'Wednesday' }, { val: '5L', label:'Thursday' }, { val: '6L', label:'Friday' }, { val: '7L', label:'Saturday' }] + const weekLAry = [{ val: '1L', label: 'Sunday' }, { val: '2L', label: 'Monday' }, { val: '3L', label: 'Tuesday' }, { val: '4L', label: 'Wednesday' }, { val: '5L', label: 'Thursday' }, { val: '6L', label: 'Friday' }, { val: '7L', label: 'Saturday' }] const childrenLAWeekAry = [] for (let i = 0; i < 7; i++) { childrenLAWeekAry.push() @@ -859,9 +861,9 @@ class ReactCron extends React.Component {
this.activeKeyChange(key)} type="card"> -
-
- +
+
+
@@ -875,9 +877,9 @@ class ReactCron extends React.Component { ))}
-
{ - steps[current].content == 'minutes' ? -
+
{ + steps[current].content == 'minutes' + ?
@@ -886,16 +888,16 @@ class ReactCron extends React.Component { Every - {let data = Object.assign({}, this.state.minutes, { incrementStart: val, minutesText: this.state.minutes.cronEvery === '2' ? `${this.state.minutes.incrementIncrement}/${val}` : this.state.minutesText }); this.setState({...this.state, minutesText: data.minutesText, minutes: data },() => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.minutes, { incrementStart: val, minutesText: this.state.minutes.cronEvery === '2' ? `${this.state.minutes.incrementIncrement}/${val}` : this.state.minutesText }); this.setState({ ...this.state, minutesText: data.minutesText, minutes: data }, () => { this.prettyCronfun() }) }}> minutes(s) starting at minutes - {let data = Object.assign({}, this.state.minutes, { incrementIncrement: val, minutesText: this.state.minutes.cronEvery === '2' ? `${val}/${this.state.minutes.incrementStart}` : this.state.minutesText }); this.setState({...this.state, minutesText: data.minutesText, minutes: data },() => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.minutes, { incrementIncrement: val, minutesText: this.state.minutes.cronEvery === '2' ? `${val}/${this.state.minutes.incrementStart}` : this.state.minutesText }); this.setState({ ...this.state, minutesText: data.minutesText, minutes: data }, () => { this.prettyCronfun() }) }}> Specific minutes (choose one or many) -
+
Every hour between - {let data = Object.assign({}, this.state.hour, { rangeStart: val, hourText: this.state.hour.cronEvery === '4' ? `${val}-${this.state.hour.rangeEnd}` : this.state.hourText }); this.setState({...this.state, hourText: data.hourText, hour: data },() => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.hour, { rangeStart: val, hourText: this.state.hour.cronEvery === '4' ? `${val}-${this.state.hour.rangeEnd}` : this.state.hourText }); this.setState({ ...this.state, hourText: data.hourText, hour: data }, () => { this.prettyCronfun() }) }}> and - {let data = Object.assign({}, this.state.hour, { rangeEnd: val, hourText: this.state.hour.cronEvery === '4' ? `${this.state.hour.rangeStart}-${val}` : this.state.hourText }); this.setState({...this.state, hourText: data.hourText, hour: data },() => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.hour, { rangeEnd: val, hourText: this.state.hour.cronEvery === '4' ? `${this.state.hour.rangeStart}-${val}` : this.state.hourText }); this.setState({ ...this.state, hourText: data.hourText, hour: data }, () => { this.prettyCronfun() }) }}>
: ''} - {steps[current].content == 'day' ? -
+ {steps[current].content == 'day' + ?
@@ -970,7 +972,7 @@ class ReactCron extends React.Component { Every - {let data = Object.assign({}, this.state.week, { incrementIncrement: val, weekText: this.state.day.cronEvery === '2' ? `${val}/${this.state.week.incrementStart}` : this.state.weekText }); this.setState({...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() })}}> + { let data = Object.assign({}, this.state.week, { incrementIncrement: val, weekText: this.state.day.cronEvery === '2' ? `${val}/${this.state.week.incrementStart}` : this.state.weekText }); this.setState({ ...this.state, weekText: data.weekText, week: data }, () => { this.prettyCronfun() }) }}> day(s) starting on Specific day of month (choose one or many) -
+
{childrenLAWeekAry} @@ -1058,7 +1060,7 @@ class ReactCron extends React.Component { On the {let data = Object.assign({}, this.state.week, { cronNthDayNth: val, weekText: this.state.day.cronEvery === '11' ? `${this.state.week.cronNthDayDay}#${val}` : this.state.weekText }); this.setState({...this.state, dayText: '?', week: data, weekText: data.weekText }, () => { this.prettyCronfun() })}}> - Every month between - {let data = Object.assign({}, this.state.month, { rangeStart: val, monthText: this.state.month.cronEvery === '4' ? `${val}-${this.state.month.rangeEnd}` : this.state.monthText }); this.setState({...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.month, { rangeStart: val, monthText: this.state.month.cronEvery === '4' ? `${val}-${this.state.month.rangeEnd}` : this.state.monthText }); this.setState({ ...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() }) }}> and - {let data = Object.assign({}, this.state.month, { rangeEnd: val, monthText: this.state.month.cronEvery === '4' ? `${this.state.month.rangeStart}-${val}` : this.state.monthText }); this.setState({...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() })}} > + { let data = Object.assign({}, this.state.month, { rangeEnd: val, monthText: this.state.month.cronEvery === '4' ? `${this.state.month.rangeStart}-${val}` : this.state.monthText }); this.setState({ ...this.state, monthText: data.monthText, month: data }, () => { this.prettyCronfun() }) }}>
: ''}
-
+
{current > 0 && ( )} {current === steps.length - 1 && ( - )} @@ -1135,11 +1137,11 @@ class ReactCron extends React.Component {
- { this.state.cornFormat ? -
+ { this.state.cornFormat + ?
Cron expression format error -
: -
+
+ :
{this.state.prettyCronText}
} diff --git a/src/models/backup.js b/src/models/backup.js index e8060faa..4117173c 100644 --- a/src/models/backup.js +++ b/src/models/backup.js @@ -16,10 +16,9 @@ export default { selectedRows: [], workloadDetailModalItem: {}, backupStatus: {}, - currentItem: {}, + currentItem: [], currentBackupVolume: {}, lastBackupUrl: '', - baseImage: '', volumeName: '', backupTargetMessage: '', previousChecked: false, @@ -28,10 +27,8 @@ export default { backupLabel: {}, nodeTags: [], diskTags: [], - bulkRestoreData: [], backupVolumesForBulkCreate: [], search: {}, - isBulkRestore: false, restoreBackupModalVisible: false, backupTargetAvailable: false, workloadDetailModalVisible: false, @@ -140,22 +137,21 @@ export default { params.backupName = lastBackup.id params.numberOfReplicas = payload.numberOfReplicas params.volumeName = lastBackup.volumeName - params.backingImage = payload.backingImage ? payload.backingImage : '' + params.backingImage = payload.backingImage || '' - yield put({ type: 'showRestoreBackupModal', payload: { currentItem: params } }) - yield put({ type: 'queryDiskTagsAndgetNodeTags' }) + yield put({ type: 'showRestoreBackupModal', payload: { currentItem: [params] } }) + yield put({ type: 'queryDiskTagsAndGetNodeTags' }) } }, *beforeShowRestoreBackupModal({ payload, }, { put }) { yield put({ type: 'showRestoreBackupModal', payload }) - yield put({ type: 'queryDiskTagsAndgetNodeTags' }) + yield put({ type: 'queryDiskTagsAndGetNodeTags' }) }, *queryBackupDetailBulkData({ payload, }, { call, put }) { - // const data = yield call(execAction, payload.url) if (payload && payload.selectedRows && payload.selectedRows.length > 0) { let data = [] @@ -173,19 +169,19 @@ export default { } } } - if (data && data.length > 0) { yield put({ type: 'showRestoreBulkBackupModal', payload: { - currentItem: { - name: '', + currentItem: data.map(d => ({ + backupName: d.name, + backingImage: d.volumeBackingImageName, + fromBackup: d.url, + volumeName: d.volumeName, numberOfReplicas: payload.numberOfReplicas, - }, - bulkRestoreData: data, + })), }, }) - - yield put({ type: 'queryDiskTagsAndgetNodeTags' }) + yield put({ type: 'queryDiskTagsAndGetNodeTags' }) } } }, @@ -197,7 +193,7 @@ export default { const search = yield select(store => { return store.backup.search }) yield put({ type: 'query', payload: { ...search } }) }, - *queryDiskTagsAndgetNodeTags({ + *queryDiskTagsAndGetNodeTags({ payload, }, { call, put }) { const nodeTags = yield call(getNodeTags, payload) @@ -212,20 +208,13 @@ export default { *restoreBulkBackup({ payload, }, { call, put, select }) { - let restoreBulkBackup = [] yield put({ type: 'hideRestoreBackupModal' }) - if (payload.bulkRestoreData && payload.selectedBackup) { - payload.bulkRestoreData.forEach((item) => { - let params = {} - Object.assign(params, payload.selectedBackup) - params.name = item.volumeName - params.fromBackup = item.url - restoreBulkBackup.push(params) - }) - } - if (restoreBulkBackup.length > 0) { - for (let i = 0; i < restoreBulkBackup.length; i++) { - yield call(restore, restoreBulkBackup[i]) + if (payload.length > 0) { + for (let i = 0; i < payload.length; i++) { + const resp = yield call(restore, payload[i]) + if (resp && resp.status === 200) { + message.success(`Successfully restore backup volume ${payload[i].name}`, 3) + } } } const search = yield select(store => { return store.backup.search }) @@ -267,7 +256,14 @@ export default { payload, }, { call, put, select }) { yield put({ type: 'hideBulkCreateVolumeStandModalVisible' }) - yield payload.map((item) => call(createVolume, item)) + if (payload.length > 0) { + for (let i = 0; i < payload.length; i++) { + const resp = yield call(createVolume, payload[i]) + if (resp && resp.status === 200) { + message.success(`Successfully create DR volume ${payload[i].name}`, 3) + } + } + } const search = yield select(store => { return store.backup.search }) yield put({ type: 'query', payload: { ...search } }) }, @@ -280,7 +276,7 @@ export default { // For DR Volume yield put({ type: 'initModalUrl', found, payload }) yield put({ type: 'showCreateVolumeStandModalVisible' }) - yield put({ type: 'queryDiskTagsAndgetNodeTags' }) + yield put({ type: 'queryDiskTagsAndGetNodeTags' }) }, *BulkCreateStandVolume({ payload, @@ -288,18 +284,18 @@ export default { const data = yield payload.backupVolume.map((item) => call(execAction, item.actions.backupList)) const volumes = data.map((item, index) => { const volume = payload.backupVolume[index] - const found = item.data.find((backup) => backup.id === volume.lastBackupName) + const lastBackup = item.data.find((backup) => backup.id === volume.lastBackupName) return { - lastBackupUrl: found.url, volumeName: volume.id, - baseImage: volume.baseImage, - size: found.volumeSize, + lastBackupUrl: lastBackup.url, + size: lastBackup.volumeSize, + backingImage: lastBackup.volumeBackingImageName, } }) // For DR Volume yield put({ type: 'initBulkCreateModalUrl', volumes }) yield put({ type: 'showBulkCreateVolumeStandModalVisible' }) - yield put({ type: 'queryDiskTagsAndgetNodeTags' }) + yield put({ type: 'queryDiskTagsAndGetNodeTags' }) }, *deleteAllBackups({ payload, @@ -426,7 +422,7 @@ export default { return { ...state, showBackuplabelsModalVisible: false, createVolumeStandModalKey: Math.random() } }, initModalUrl(state, action) { - return { ...state, lastBackupUrl: action.found.url, volumeName: action.payload.name, baseImage: action.payload.baseImage, size: action.found.volumeSize } + return { ...state, lastBackupUrl: action.found.url, volumeName: action.payload.name, size: action.found.volumeSize } }, initBulkCreateModalUrl(state, action) { return { ...state, backupVolumesForBulkCreate: action.volumes } @@ -435,10 +431,10 @@ export default { return { ...state, ...action.payload, restoreBackupModalVisible: true, restoreBackupModalKey: Math.random() } }, showRestoreBulkBackupModal(state, action) { - return { ...state, ...action.payload, isBulkRestore: true, restoreBackupModalVisible: true, restoreBackupModalKey: Math.random() } + return { ...state, ...action.payload, restoreBackupModalVisible: true, restoreBackupModalKey: Math.random() } }, hideRestoreBackupModal(state) { - return { ...state, previousChecked: false, tagsLoading: true, restoreBackupModalVisible: false, isBulkRestore: false } + return { ...state, previousChecked: false, tagsLoading: true, restoreBackupModalVisible: false } }, updateSorter(state, action) { saveSorter('backupList.sorter', action.payload) diff --git a/src/routes/backup/BackupDetail.js b/src/routes/backup/BackupDetail.js index 199e433e..fa62df06 100644 --- a/src/routes/backup/BackupDetail.js +++ b/src/routes/backup/BackupDetail.js @@ -3,19 +3,18 @@ import PropTypes from 'prop-types' import { connect } from 'dva' import queryString from 'query-string' import { Modal } from 'antd' -import RestoreBackup from './RestoreBackup' +import RestoreBackupModal from './RestoreBackupModal' import { DropOption } from '../../components' import BackupList from './BackupList' import { sortBackups } from '../../utils/sort' import ShowBackupLabels from './ShowBackupLabels' -import CreateStandbyVolume from './CreateStandbyVolume' +import CreateStandbyVolumeModal from './CreateStandbyVolumeModal' import WorkloadDetailModal from '../volume/WorkloadDetailModal' const { confirm } = Modal -function Backup({ host, backup, volume, setting, backingImage, loading, location, dispatch }) { +function Backup({ backup, volume, setting, backingImage, loading, location, dispatch }) { const { backupVolumes, backupData, restoreBackupModalVisible, restoreBackupModalKey, currentItem, sorter, showBackupLabelsModalKey, backupLabel, showBackuplabelsModalVisible, createVolumeStandModalKey, createVolumeStandModalVisible, baseImage, size, lastBackupUrl, workloadDetailModalVisible, workloadDetailModalItem, workloadDetailModalKey, previousChecked, tagsLoading, nodeTags, diskTags } = backup - const hosts = host.data const volumeList = volume.data const settings = setting.data const backingImages = backingImage.data @@ -82,7 +81,6 @@ function Backup({ host, backup, volume, setting, backingImage, loading, location const restoreBackupModalProps = { item: currentItem, - hosts, previousChecked, tagsLoading, nodeTags, @@ -207,9 +205,9 @@ function Backup({ host, backup, volume, setting, backingImage, loading, location />
- { restoreBackupModalVisible ? : ''} + { restoreBackupModalVisible ? : ''} { showBackuplabelsModalVisible ? : ''} - { createVolumeStandModalVisible ? : ''} + { createVolumeStandModalVisible ? : ''} { workloadDetailModalVisible ? : ''}
) @@ -220,14 +218,13 @@ Backup.propTypes = { location: PropTypes.object, dispatch: PropTypes.func, loading: PropTypes.bool, - host: PropTypes.object, setting: PropTypes.object, volume: PropTypes.object, backingImage: PropTypes.object, } export default connect(({ - host, backup, setting, loading, volume, backingImage, + backup, setting, loading, volume, backingImage, }) => ({ - host, backup, setting, loading: loading.models.backup, volume, backingImage, + backup, setting, loading: loading.models.backup, volume, backingImage, }))(Backup) diff --git a/src/routes/backup/BulkCreateStandbyVolumeModal.js b/src/routes/backup/BulkCreateStandbyVolumeModal.js index a043608e..cb51eeaa 100644 --- a/src/routes/backup/BulkCreateStandbyVolumeModal.js +++ b/src/routes/backup/BulkCreateStandbyVolumeModal.js @@ -1,8 +1,10 @@ -import React from 'react' +import React, { useState } from 'react' import PropTypes from 'prop-types' -import { Form, InputNumber, Select, Spin } from 'antd' +import { Form, InputNumber, Select, message, Spin, Checkbox, Tooltip, Tabs, Button, Input, Popover, Alert } from 'antd' import { ModalBlur } from '../../components' import { formatMib } from '../../utils/formater' + +const TabPane = Tabs.TabPane const FormItem = Form.Item const { Option } = Select @@ -24,6 +26,7 @@ const modal = ({ nodeTags, diskTags, tagsLoading, + backupVolumes, backingImages, v1DataEngineEnabled, v2DataEngineEnabled, @@ -31,32 +34,188 @@ const modal = ({ getFieldDecorator, validateFields, getFieldsValue, + getFieldValue, + getFieldsError, + setFieldsValue, }, }) => { + const initConfigs = items.map((i) => ({ + name: i.volumeName, + size: formatMib(i.size), + numberOfReplicas, + dataEngine: 'v1', + accessMode: i.accessMode || null, + backingImage: i.backingImage, + encrypted: false, + nodeSelector: [], + diskSelector: [], + })) + const [currentTab, setCurrentTab] = useState(0) + const [drVolumeConfigs, setDrVolumeConfigs] = useState(initConfigs) + function handleOk() { - validateFields((errors) => { - if (errors) { - return - } + const data = drVolumeConfigs.map((config, index) => ({ + ...config, + standby: true, + frontend: '', + fromBackup: items[index].fromBackup, + size: items[index].size.replace(/\s/ig, ''), + })) + onOk(data) + } + + + const handleApplyAll = () => { + // only apply below configs to other configs + const currentConfig = { + numberOfReplicas: getFieldValue('numberOfReplicas'), + dataEngine: getFieldValue('dataEngine'), + accessMode: getFieldValue('accessMode'), + encrypted: getFieldValue('encrypted') || false, + nodeSelector: getFieldValue('nodeSelector'), + diskSelector: getFieldValue('diskSelector'), + } + setDrVolumeConfigs(prev => { + const newConfigs = [...prev] + newConfigs.forEach((config, index) => { + if (index !== currentTab) { + newConfigs.splice(index, 1, { ...config, ...currentConfig }) + } + }) + return newConfigs + }) + message.success(`Successfully apply ${getFieldValue('name')} config to all other disaster recovery volumes`, 5) + } + + const allFieldsError = { ...getFieldsError() } + const hasFieldsError = Object.values(allFieldsError).some(fieldError => fieldError !== undefined) || false + + const updateDrVolumeConfigs = (key, newValue) => { + setDrVolumeConfigs(prev => { + const newConfigs = [...prev] const data = { ...getFieldsValue(), + [key]: newValue, } - onOk(data, items) + newConfigs.splice(currentTab, 1, data) + return newConfigs + }) + } + + const handleNameChange = (e) => updateDrVolumeConfigs('name', e.target.value) + const handleReplicasNumberChange = (newNumber) => updateDrVolumeConfigs('numberOfReplicas', newNumber) + const handleEncryptedCheck = (e) => updateDrVolumeConfigs('encrypted', e.target.checked) + const handleDataEngineChange = (value) => updateDrVolumeConfigs('dataEngine', value) + const handleAccessModeChange = (value) => updateDrVolumeConfigs('accessMode', value) + const handleNodeTagRemove = (value) => { + const oldNodeTags = drVolumeConfigs[currentTab]?.nodeSelector + const newNodeSelector = oldNodeTags?.filter(tag => tag !== value) || [] + updateDrVolumeConfigs('nodeSelector', newNodeSelector) + } + const handleNodeTagAdd = (value) => { + const oldNodeTags = drVolumeConfigs[currentTab]?.nodeSelector + updateDrVolumeConfigs('nodeSelector', [...oldNodeTags, value]) + } + const handleDiskTagRemove = (value) => { + const oldDiskTags = drVolumeConfigs[currentTab]?.diskSelector + const newDiskSelector = oldDiskTags?.filter(tag => tag !== value) || [] + updateDrVolumeConfigs('diskSelector', newDiskSelector) + } + const handleDiskTagAdd = (value) => { + const oldDiskTags = drVolumeConfigs[currentTab]?.diskSelector + updateDrVolumeConfigs('diskSelector', [...oldDiskTags, value]) + } + + const handleTabClick = (key) => { + if (hasFieldsError) { + message.error('Please correct the error fields before switching to another tab', 5) + return + } + validateFields((errors) => { + if (errors) return errors }) + + const newIndex = items.findIndex(i => i.volumeName === key) + if (newIndex !== -1) { + setCurrentTab(newIndex) + const nextConfig = drVolumeConfigs[newIndex] + setFieldsValue({ + name: nextConfig.name, + size: nextConfig.size, + numberOfReplicas: nextConfig.numberOfReplicas, + dataEngine: nextConfig.dataEngine, + accessMode: nextConfig.accessMode, + backingImage: nextConfig.backingImage, + encrypted: nextConfig.encrypted, + nodeSelector: nextConfig.nodeSelector, + diskSelector: nextConfig.diskSelector, + }) + } } + const tooltipTitle = `Apply this ${getFieldValue('name')} config to all the other disaster recovery volumes, this action will overwrite your previous filled in configs` const modalOpts = { - title: 'Create Disaster Recovery Volume', + title: 'Create Multiple Disaster Recovery Volumes', visible, - onCancel, onOk: handleOk, + onCancel, + width: 700, + footer: [ + , + + + , + , + ], } - const selectedItems = items.map((item) => `${item.name} ${formatMib(item.size)}`).join(', ') + const showWarning = backupVolumes?.some((backupVolume) => backupVolume.name === getFieldValue('name')) + const alertMessage =

+ If there is another volume with the same name ({getFieldsValue().name}), the create DR volume will fail. +

+ const item = drVolumeConfigs[currentTab] || {} + const activeKey = items[currentTab].volumeName + return ( + + {items.map(i => )} +
- - { selectedItems } + + +
+ }> + + {getFieldDecorator('name', { + initialValue: item.name, + rules: [ + { + required: true, + message: 'Volume name is required', + }, + ], + })()} + + + + {getFieldDecorator('size', { + initialValue: item.size, + rules: [ + { + required: true, + message: 'Please input volume size', + }, + ], + })()} {getFieldDecorator('numberOfReplicas', { @@ -66,23 +225,8 @@ const modal = ({ required: true, message: 'Please input the number of replicas', }, - { - validator: (rule, value, callback) => { - if (value === '' || typeof value !== 'number') { - callback() - return - } - if (value < 1 || value > 10) { - callback('The value should be between 1 and 10') - } else if (!/^\d+$/.test(value)) { - callback('The value must be a positive integer') - } else { - callback() - } - }, - }, ], - })()} + })()} {getFieldDecorator('dataEngine', { @@ -93,7 +237,7 @@ const modal = ({ message: 'Please select the data engine', }, { - validator: (rule, value, callback) => { + validator: (_rule, value, callback) => { if (value === 'v1' && !v1DataEngineEnabled) { callback('v1 data engine is not enabled') } else if (value === 'v2' && !v2DataEngineEnabled) { @@ -103,31 +247,37 @@ const modal = ({ }, }, ], - })( )} {getFieldDecorator('accessMode', { - initialValue: 'rwo', - })( )} {getFieldDecorator('backingImage', { - initialValue: '', - })( { backingImages.map(backingImage => ) } )} + + {getFieldDecorator('encrypted', { + valuePropName: 'checked', + initialValue: item.encrypted || false, + })()} + {getFieldDecorator('nodeSelector', { initialValue: [], - })( { nodeTags.map(opt => ) } )} @@ -136,7 +286,7 @@ const modal = ({ {getFieldDecorator('diskSelector', { initialValue: [], - })( { diskTags.map(opt => ) } )} @@ -155,6 +305,7 @@ modal.propTypes = { numberOfReplicas: PropTypes.number, nodeTags: PropTypes.array, diskTags: PropTypes.array, + backupVolumes: PropTypes.array, tagsLoading: PropTypes.bool, backingImages: PropTypes.array, v1DataEngineEnabled: PropTypes.bool, diff --git a/src/routes/backup/BulkRestoreBackupModal.js b/src/routes/backup/BulkRestoreBackupModal.js new file mode 100644 index 00000000..0646c9df --- /dev/null +++ b/src/routes/backup/BulkRestoreBackupModal.js @@ -0,0 +1,316 @@ +import React, { useState } from 'react' +import PropTypes from 'prop-types' +import { Form, Input, InputNumber, Spin, Select, message, Popover, Alert, Tabs, Button, Checkbox, Tooltip } from 'antd' +import { ModalBlur } from '../../components' + +const TabPane = Tabs.TabPane +const FormItem = Form.Item +const Option = Select.Option + +const formItemLayout = { + labelCol: { + span: 8, + }, + wrapperCol: { + span: 14, + }, +} + +const modal = ({ + items, + visible, + onCancel, + onOk, + nodeTags, + diskTags, + tagsLoading, + backingImages, + backupVolumes, + v1DataEngineEnabled, + v2DataEngineEnabled, + form: { + getFieldDecorator, + validateFields, + getFieldsError, + getFieldsValue, + getFieldValue, + setFieldsValue, + }, +}) => { + const initConfigs = items.map((i) => ({ + name: i.volumeName, + numberOfReplicas: i.numberOfReplicas, + dataEngine: 'v1', + accessMode: i.accessMode || null, + latestBackup: i.backupName, + backingImage: i.backingImage, + encrypted: false, + restoreVolumeRecurringJob: 'ignored', + nodeSelector: i.nodeSelector || [], + diskSelector: i.diskSelector || [], + })) + const [currentTab, setCurrentTab] = useState(0) + const [restoreBackupConfigs, setRestoreBackupConfigs] = useState(initConfigs) + + const handleOk = () => onOk(restoreBackupConfigs) + + const updateRestoreBackupConfigs = (key, newValue) => { + setRestoreBackupConfigs(prev => { + const newConfigs = [...prev] + const data = { + ...getFieldsValue(), + [key]: newValue, + fromBackup: items[currentTab]?.fromBackup || '', + } + newConfigs.splice(currentTab, 1, data) + return newConfigs + }) + } + + const handleNameChange = (e) => updateRestoreBackupConfigs('name', e.target.value) + const handleReplicasNumberChange = (newNumber) => updateRestoreBackupConfigs('numberOfReplicas', newNumber) + const handleEncryptedCheck = (e) => updateRestoreBackupConfigs('encrypted', e.target.checked) + const handleDataEngineChange = (value) => updateRestoreBackupConfigs('dataEngine', value) + const handleAccessModeChange = (value) => updateRestoreBackupConfigs('accessMode', value) + const handleRecurringJobChange = (value) => updateRestoreBackupConfigs('restoreVolumeRecurringJob', value) + const handleNodeTagRemove = (value) => { + const oldNodeTags = restoreBackupConfigs[currentTab]?.nodeSelector + const newNodeSelector = oldNodeTags?.filter(tag => tag !== value) || [] + updateRestoreBackupConfigs('nodeSelector', newNodeSelector) + } + const handleNodeTagAdd = (value) => { + const oldNodeTags = restoreBackupConfigs[currentTab]?.nodeSelector + updateRestoreBackupConfigs('nodeSelector', [...oldNodeTags, value]) + } + const handleDiskTagRemove = (value) => { + const oldDiskTags = restoreBackupConfigs[currentTab]?.diskSelector + const newDiskSelector = oldDiskTags?.filter(tag => tag !== value) || [] + updateRestoreBackupConfigs('diskSelector', newDiskSelector) + } + const handleDiskTagAdd = (value) => { + const oldDiskTags = restoreBackupConfigs[currentTab]?.diskSelector + updateRestoreBackupConfigs('diskSelector', [...oldDiskTags, value]) + } + + const handleApplyAll = () => { + // only apply below configs to other configs + const currentConfig = { + numberOfReplicas: getFieldValue('numberOfReplicas'), + dataEngine: getFieldValue('dataEngine'), + accessMode: getFieldValue('accessMode'), + encrypted: getFieldValue('encrypted') || false, + restoreVolumeRecurringJob: getFieldValue('restoreVolumeRecurringJob'), + nodeSelector: getFieldValue('nodeSelector'), + diskSelector: getFieldValue('diskSelector'), + } + setRestoreBackupConfigs(prev => { + const newConfigs = [...prev] + newConfigs.forEach((config, index) => { + if (index !== currentTab) { + newConfigs.splice(index, 1, { ...config, ...currentConfig }) + } + }) + return newConfigs + }) + message.success(`Successfully apply ${getFieldValue('name')} config to all other restore volumes`, 5) + } + + const allFieldsError = { ...getFieldsError() } + const hasFieldsError = Object.values(allFieldsError).some(fieldError => fieldError !== undefined) || false + + const handleTabClick = (key) => { + if (hasFieldsError) { + message.error('Please correct the error fields before switching to another tab', 5) + return + } + validateFields((errors) => { + if (errors) return errors + }) + + const newIndex = items.findIndex(i => i.backupName === key) + + if (newIndex !== -1) { + setCurrentTab(newIndex) + const nextConfig = restoreBackupConfigs[newIndex] + setFieldsValue({ + name: nextConfig.name, + numberOfReplicas: nextConfig.numberOfReplicas, + dataEngine: nextConfig.dataEngine, + accessMode: nextConfig.accessMode, + latestBackup: nextConfig.latestBackup, + backingImage: nextConfig.backingImage, + encrypted: nextConfig.encrypted, + restoreVolumeRecurringJob: nextConfig.restoreVolumeRecurringJob, + nodeSelector: nextConfig.nodeSelector, + diskSelector: nextConfig.diskSelector, + }) + } + } + + const showWarning = backupVolumes?.some((backupVolume) => backupVolume.name === getFieldsValue().name) + const alertMessage =

+ 1. If there is another volume with the same name ({getFieldsValue().name}), the restore action will fail. +
+ 2. The restore volume name ({getFieldsValue().name}) is the same as this backup volume, by which the backups created after restoration reside in this backup volume as well. +

+ + const tooltipTitle = `Apply this ${getFieldValue('name')} config to all the other restore volumes, this action will overwrite your previous filled in configs` + const modalOpts = { + title: 'Restore Multiple Latest Backups', + visible, + onOk: handleOk, + onCancel, + width: 700, + footer: [ + , + + + , + , + ], + } + + const item = restoreBackupConfigs[currentTab] || {} + const activeKey = item.latestBackup + + return ( + + + {items.map(i => )} + + + + +
+ }> + + {getFieldDecorator('name', { + initialValue: item.name, + rules: [ + { + required: true, + message: 'Volume name is required', + }, + ], + })()} + + + + {getFieldDecorator('numberOfReplicas', { + initialValue: item.numberOfReplicas, + rules: [ + { + required: true, + message: 'Please input the number of replicas', + }, + ], + })() + } + + + {getFieldDecorator('dataEngine', { + initialValue: item.dataEngine || 'v1', + rules: [ + { + required: true, + message: 'Please select the data engine', + }, + { + validator: (_rule, value, callback) => { + if (value === 'v1' && !v1DataEngineEnabled) { + callback('v1 data engine is not enabled') + } else if (value === 'v2' && !v2DataEngineEnabled) { + callback('v2 data engine is not enabled') + } + callback() + }, + }, + ], + })()} + + + {getFieldDecorator('accessMode', { + initialValue: item.accessMode, + })()} + + + {getFieldDecorator('latestBackup', { + initialValue: item.latestBackup, + })()} + + + {getFieldDecorator('backingImage', { + initialValue: item.backingImage, + })()} + + + {getFieldDecorator('encrypted', { + valuePropName: 'checked', + initialValue: item.encrypted || false, + })()} + + + {getFieldDecorator('restoreVolumeRecurringJob', { + initialValue: 'ignored', + })()} + + + + {getFieldDecorator('nodeSelector', { + initialValue: [], + })()} + + + + + {getFieldDecorator('diskSelector', { + initialValue: [], + })()} + + + + + ) +} + +modal.propTypes = { + items: PropTypes.array.isRequired, + visible: PropTypes.bool, + onCancel: PropTypes.func, + onOk: PropTypes.func, + nodeTags: PropTypes.array, + diskTags: PropTypes.array, + backingImages: PropTypes.array, + backupVolumes: PropTypes.array, + v1DataEngineEnabled: PropTypes.bool, + v2DataEngineEnabled: PropTypes.bool, + tagsLoading: PropTypes.bool, + form: PropTypes.object.isRequired, +} + +export default Form.create()(modal) diff --git a/src/routes/backup/CreateStandbyVolume.js b/src/routes/backup/CreateStandbyVolumeModal.js similarity index 94% rename from src/routes/backup/CreateStandbyVolume.js rename to src/routes/backup/CreateStandbyVolumeModal.js index 05d30fbb..bd00123f 100644 --- a/src/routes/backup/CreateStandbyVolume.js +++ b/src/routes/backup/CreateStandbyVolumeModal.js @@ -50,6 +50,7 @@ const modal = ({ visible, onCancel, onOk: handleOk, + width: 700, } const showWarning = backupVolumes?.some((backupVolume) => backupVolume.name === getFieldsValue().name) @@ -69,7 +70,7 @@ const modal = ({ rules: [ { required: true, - message: 'Please input volume name', + message: 'Volume name is required', }, ], })()} @@ -98,7 +99,7 @@ const modal = ({ }, }, ], - })()} + })()} {getFieldDecorator('dataEngine', { @@ -133,7 +134,7 @@ const modal = ({ message: 'Please input the number of replicas', }, { - validator: (rule, value, callback) => { + validator: (_rule, value, callback) => { if (value === '' || typeof value !== 'number') { callback() return @@ -161,15 +162,15 @@ const modal = ({ {getFieldDecorator('backingImage', { initialValue: item.backingImage, - })( { backingImages.map(backingImage => ) } )} - {getFieldDecorator('encrypted', { - valuePropName: 'encrypted', - initialValue: false, - })()} + {getFieldDecorator('encrypted', { + valuePropName: 'encrypted', + initialValue: false, + })()} @@ -213,7 +214,6 @@ modal.propTypes = { onCancel: PropTypes.func, item: PropTypes.object, onOk: PropTypes.func, - hosts: PropTypes.array, nodeTags: PropTypes.array, diskTags: PropTypes.array, backupVolumes: PropTypes.array, diff --git a/src/routes/backup/RestoreBackup.js b/src/routes/backup/RestoreBackupModal.js similarity index 77% rename from src/routes/backup/RestoreBackup.js rename to src/routes/backup/RestoreBackupModal.js index 1abbc018..358de293 100644 --- a/src/routes/backup/RestoreBackup.js +++ b/src/routes/backup/RestoreBackupModal.js @@ -28,7 +28,6 @@ const modal = ({ setPreviousChange, v1DataEngineEnabled, v2DataEngineEnabled, - isBulk = false, form: { getFieldDecorator, validateFields, @@ -51,8 +50,9 @@ const modal = ({ onOk(data) }) } + const modalOpts = { - title: isBulk ? 'Restore Backup' : `Restore Backup ${item.backupName}`, + title: `Restore Backup ${item.backupName}`, visible, onCancel, onOk: handleOk, @@ -74,7 +74,7 @@ const modal = ({
+ content={
}> @@ -82,37 +82,27 @@ const modal = ({ initialValue: item.name, rules: [ { - required: true && !isBulk, - message: 'Please input volume name', + required: true, + message: 'Volume name is required', }, ], - })()} + })()}
- {!isBulk ? - - : ''} - {!isBulk ? - {getFieldDecorator('numberOfReplicas', { - initialValue: item.numberOfReplicas, - rules: [ - { - required: true, - message: 'Please input the number of replicas', - }, - ], - })()} - : - {getFieldDecorator('numberOfReplicas', { - initialValue: item.numberOfReplicas, - rules: [ - { - required: true, - message: 'Please input the number of replicas', - }, - ], - })()} - } + + + + + {getFieldDecorator('numberOfReplicas', { + initialValue: item.numberOfReplicas, + rules: [ + { + required: true, + message: 'Please input the number of replicas', + }, + ], + })()} + {getFieldDecorator('dataEngine', { initialValue: 'v1', @@ -148,15 +138,15 @@ const modal = ({ {getFieldDecorator('backingImage', { initialValue: item.backingImage, - })( { backingImages.map(backingImage => ) } )} - {getFieldDecorator('encrypted', { - valuePropName: 'encrypted', - initialValue: false, - })()} + {getFieldDecorator('encrypted', { + valuePropName: 'encrypted', + initialValue: false, + })()} {getFieldDecorator('restoreVolumeRecurringJob', { @@ -191,22 +181,20 @@ const modal = ({ } modal.propTypes = { - form: PropTypes.object.isRequired, + item: PropTypes.object, visible: PropTypes.bool, previousChecked: PropTypes.bool, onCancel: PropTypes.func, - item: PropTypes.object, onOk: PropTypes.func, setPreviousChange: PropTypes.func, - hosts: PropTypes.array, nodeTags: PropTypes.array, diskTags: PropTypes.array, backingImages: PropTypes.array, backupVolumes: PropTypes.array, v1DataEngineEnabled: PropTypes.bool, v2DataEngineEnabled: PropTypes.bool, - isBulk: PropTypes.bool, tagsLoading: PropTypes.bool, + form: PropTypes.object.isRequired, } export default Form.create()(modal) diff --git a/src/routes/backup/index.js b/src/routes/backup/index.js index 93813972..bfba0329 100644 --- a/src/routes/backup/index.js +++ b/src/routes/backup/index.js @@ -5,21 +5,23 @@ import { routerRedux } from 'dva/router' import { Row, Col, Modal, Descriptions } from 'antd' import BackupVolumeList from './BackupVolumeList' import queryString from 'query-string' -import RestoreBackup from './RestoreBackup' -import CreateStandbyVolume from './CreateStandbyVolume' +import RestoreBackupModal from './RestoreBackupModal' +import BulkRestoreBackupModal from './BulkRestoreBackupModal' +import CreateStandbyVolumeModal from './CreateStandbyVolumeModal' import BulkCreateStandbyVolumeModal from './BulkCreateStandbyVolumeModal' + import { Filter } from '../../components/index' import BackupBulkActions from './BackupBulkActions' import WorkloadDetailModal from '../volume/WorkloadDetailModal' const { confirm, info } = Modal -function Backup({ host, backup, loading, setting, backingImage, dispatch, location }) { +function Backup({ backup, loading, setting, backingImage, dispatch, location }) { location.search = location.search ? location.search : '' // currentItem || currentBackupVolume. The currentItem was a wrong decision at the beginning of the design. It was originally to simplify the transfer of attributes without complete assignment. // When backup supports ws, currentItem will be refactored to currentBackupVolume - const { backupVolumes, sorter, backupFilterKey, currentItem, restoreBackupModalKey, createVolumeStandModalKey, bulkCreateVolumeStandModalKey, createVolumeStandModalVisible, bulkCreateVolumeStandModalVisible, lastBackupUrl, baseImage, size, restoreBackupModalVisible, selectedRows, isBulkRestore, bulkRestoreData, previousChecked, tagsLoading, nodeTags, diskTags, volumeName, backupVolumesForBulkCreate, workloadDetailModalVisible, WorkloadDetailModalKey, workloadDetailModalItem, currentBackupVolume } = backup - const hosts = host.data + const { backupVolumes, sorter, backupFilterKey, currentItem, restoreBackupModalKey, createVolumeStandModalKey, bulkCreateVolumeStandModalKey, createVolumeStandModalVisible, bulkCreateVolumeStandModalVisible, lastBackupUrl, size, restoreBackupModalVisible, selectedRows, previousChecked, tagsLoading, nodeTags, diskTags, volumeName, backupVolumesForBulkCreate, workloadDetailModalVisible, WorkloadDetailModalKey, workloadDetailModalItem, currentBackupVolume } = backup + const settings = setting.data const backingImages = backingImage.data const defaultReplicaCountSetting = settings.find(s => s.id === 'default-replica-count') @@ -66,7 +68,7 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati payload: { field: s.field, order: s.order, columnKey: s.columnKey }, }) }, - Create(record) { + Create(record) { // to create DR volume dispatch({ type: 'backup/CreateStandVolume', payload: record, @@ -106,16 +108,15 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati }) }, onRowClick(record, flag) { - let selecteRowByClick = [record] - + let selectedRowByClick = [record] if (flag) { selectedRows.forEach((item) => { - if (selecteRowByClick.every((ele) => { + if (selectedRowByClick.every((ele) => { return ele.id !== item.id })) { - selecteRowByClick.push(item) + selectedRowByClick.push(item) } else { - selecteRowByClick = selecteRowByClick.filter((ele) => { + selectedRowByClick = selectedRowByClick.filter((ele) => { return ele.id !== item.id }) } @@ -125,7 +126,7 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati dispatch({ type: 'backup/changeSelection', payload: { - selectedRows: selecteRowByClick, + selectedRows: selectedRowByClick, }, }) }, @@ -164,9 +165,31 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati }, } + const bulkRestoreBackupModalProps = { + items: currentItem, + tagsLoading, + nodeTags, + diskTags, + backingImages, + backupVolumes, + v1DataEngineEnabled, + v2DataEngineEnabled, + visible: restoreBackupModalVisible, + onOk(selectedBackupConfigs) { + dispatch({ + type: 'backup/restoreBulkBackup', + payload: selectedBackupConfigs, + }) + }, + onCancel() { + dispatch({ + type: 'backup/hideRestoreBackupModal', + }) + }, + } + const restoreBackupModalProps = { - item: currentItem, - hosts, + item: currentItem[0] || {}, tagsLoading, nodeTags, diskTags, @@ -175,23 +198,12 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati backupVolumes, v1DataEngineEnabled, v2DataEngineEnabled, - isBulk: isBulkRestore, visible: restoreBackupModalVisible, onOk(selectedBackup) { - if (isBulkRestore) { - dispatch({ - type: 'backup/restoreBulkBackup', - payload: { - bulkRestoreData, - selectedBackup, - }, - }) - } else { - dispatch({ - type: 'backup/restore', - payload: selectedBackup, - }) - } + dispatch({ + type: 'backup/restore', + payload: selectedBackup, + }) }, setPreviousChange(checked) { dispatch({ @@ -237,11 +249,9 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati item: { numberOfReplicas: defaultNumberOfReplicas, size, - iops: 1000, - baseImage, fromBackup: lastBackupUrl, name: volumeName, - backingImage: currentBackupVolume ? currentBackupVolume.backingImageName : '', + backingImage: currentBackupVolume?.backingImageName || '', }, visible: createVolumeStandModalVisible, nodeTags, @@ -269,29 +279,23 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati const bulkCreateVolumeStandModalProps = { items: backupVolumesForBulkCreate.map((item) => ({ size: item.size, - // baseImage: item.baseImage, fromBackup: item.lastBackupUrl, - name: item.volumeName, + volumeName: item.volumeName, + backingImage: item.backingImage, })), numberOfReplicas: defaultNumberOfReplicas, visible: bulkCreateVolumeStandModalVisible, nodeTags, diskTags, tagsLoading, + backupVolumes, backingImages, v1DataEngineEnabled, v2DataEngineEnabled, - onOk(params, newVolumes) { - let data = newVolumes.map((item) => ({ - ...item, - ...params, - standby: true, - frontend: '', - size: item.size.replace(/\s/ig, ''), - })) + onOk(drVolumeConfigs) { dispatch({ type: 'backup/bulkCreateVolume', - payload: data, + payload: drVolumeConfigs, }) }, onCancel() { @@ -323,8 +327,9 @@ function Backup({ host, backup, loading, setting, backingImage, dispatch, locati - { restoreBackupModalVisible ? : ''} - { createVolumeStandModalVisible ? : ''} + { restoreBackupModalVisible && currentItem.length === 1 && } + { restoreBackupModalVisible && currentItem.length > 1 && } + { createVolumeStandModalVisible ? : ''} { bulkCreateVolumeStandModalVisible ? : ''} { workloadDetailModalVisible ? : ''}
@@ -336,7 +341,6 @@ Backup.propTypes = { location: PropTypes.object, dispatch: PropTypes.func, loading: PropTypes.bool, - host: PropTypes.object, setting: PropTypes.object, backingImage: PropTypes.object, nodeTags: PropTypes.array, @@ -344,4 +348,4 @@ Backup.propTypes = { tagsLoading: PropTypes.bool, } -export default connect(({ host, backup, setting, backingImage, loading }) => ({ host, backup, setting, backingImage, loading: loading.models.backup }))(Backup) +export default connect(({ backup, setting, backingImage, loading }) => ({ backup, setting, backingImage, loading: loading.models.backup }))(Backup) diff --git a/src/routes/recurringJob/CreateRecurringJob.js b/src/routes/recurringJob/CreateRecurringJob.js index fca44a02..f6386bb6 100644 --- a/src/routes/recurringJob/CreateRecurringJob.js +++ b/src/routes/recurringJob/CreateRecurringJob.js @@ -318,7 +318,7 @@ const modal = ({ required: true, }, ], - })()} + })()} {getFieldDecorator('concurrency', { diff --git a/src/routes/volume/detail/CreateRecurringJob.js b/src/routes/volume/detail/CreateRecurringJob.js index c57b09ff..0d08d44a 100644 --- a/src/routes/volume/detail/CreateRecurringJob.js +++ b/src/routes/volume/detail/CreateRecurringJob.js @@ -290,7 +290,7 @@ const modal = ({ return ( - +