diff --git a/.gitignore b/.gitignore index ff012263ac..34a1868f30 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,11 @@ web/.nyc_output web/coverage web/cypress/screenshots/ web/cypress/videos/ +web/cypress/downloads/ +web/cypress/screenshots/ +web/cypress/videos/ + +# vim +*.swp +*.swo + diff --git a/web/config/routes.ts b/web/config/routes.ts index 7f01bc3344..72057b5a15 100644 --- a/web/config/routes.ts +++ b/web/config/routes.ts @@ -39,6 +39,10 @@ const routes = [ path: '/routes/:rid/edit', component: './Route/Create', }, + { + path: '/routes/:rid/duplicate', + component: './Route/Create', + }, { path: '/ssl/:id/edit', component: './SSL/Create', diff --git a/web/cypress/integration/route/create-edit-delete-route.spec.js b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js similarity index 79% rename from web/cypress/integration/route/create-edit-delete-route.spec.js rename to web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js index 744e54db51..85aad6f2e7 100644 --- a/web/cypress/integration/route/create-edit-delete-route.spec.js +++ b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js @@ -19,6 +19,7 @@ context('Create and Delete Route', () => { const name = `routeName${new Date().valueOf()}`; const newName = `newName${new Date().valueOf()}`; + const duplicateNewName = `duplicateName${new Date().valueOf()}`; const sleepTime = 100; const timeout = 5000; @@ -153,12 +154,46 @@ context('Create and Delete Route', () => { }); }); - it('should delete the route', function () { - cy.visit('/routes/list'); + + it('should duplicate the route', function () { + cy.visit('/'); + cy.contains('Route').click(); + cy.get(this.domSelector.nameSelector).type(newName); cy.contains('Search').click(); - cy.contains(newName).siblings().contains('Delete').click(); - cy.contains('button', 'Confirm').click(); - cy.get(this.domSelector.notification).should('contain', this.data.deleteRouteSuccess); + cy.contains(newName).siblings().contains('Duplicate').click(); + + cy.get(this.domSelector.name).clear().type(duplicateNewName); + cy.get(this.domSelector.description).clear().type(this.data.description2); + cy.contains('Next').click(); + cy.contains('Next').click(); + cy.contains('Next').click(); + cy.contains('Submit').click(); + cy.contains(this.data.submitSuccess); + cy.contains('Goto List').click(); + cy.url().should('contains', 'routes/list'); + cy.contains(duplicateNewName).siblings().should('contain', this.data.description2); + + // test view + cy.contains(duplicateNewName).siblings().contains('View').click(); + cy.get(this.domSelector.drawer).should('be.visible'); + + cy.get(this.domSelector.codemirrorScroll).within(() => { + cy.contains('upstream').should("exist"); + cy.contains(duplicateNewName).should('exist'); + }); + }); + + it('should delete the route', function () { + cy.visit('/routes/list'); + const { domSelector, data } = this; + const routeNames = [newName, duplicateNewName]; + routeNames.forEach(function (routeName) { + cy.get(domSelector.name).clear().type(routeName); + cy.contains('Search').click(); + cy.contains(routeName).siblings().contains('Delete').click(); + cy.contains('button', 'Confirm').click(); + cy.get(domSelector.notification).should('contain', data.deleteRouteSuccess); + }); }); }); diff --git a/web/src/locales/en-US/component.ts b/web/src/locales/en-US/component.ts index 061ce407a8..a6a41000d5 100644 --- a/web/src/locales/en-US/component.ts +++ b/web/src/locales/en-US/component.ts @@ -35,6 +35,7 @@ export default { 'component.global.save': 'Save', 'component.global.edit': 'Configure', 'component.global.view': 'View', + 'component.global.duplicate': 'Duplicate', 'component.global.manage': 'Manage', 'component.global.update': 'Update', 'component.global.get': 'Get', diff --git a/web/src/locales/zh-CN/component.ts b/web/src/locales/zh-CN/component.ts index 494856d0db..04b7161256 100644 --- a/web/src/locales/zh-CN/component.ts +++ b/web/src/locales/zh-CN/component.ts @@ -35,6 +35,7 @@ export default { 'component.global.save': '保存', 'component.global.edit': '编辑', 'component.global.view': '查看', + 'component.global.duplicate': '复制', 'component.global.manage': '管理', 'component.global.update': '更新', 'component.global.get': '获取', diff --git a/web/src/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx index 30bb2109ba..648546f350 100644 --- a/web/src/pages/Route/Create.tsx +++ b/web/src/pages/Route/Create.tsx @@ -88,7 +88,7 @@ const Page: React.FC = (props) => { }; useEffect(() => { - if (props.route.path.indexOf('edit') !== -1) { + if (props.route.path.indexOf('edit') !== -1 || props.route.path.indexOf('duplicate') !== -1) { setupRoute(props.match.params.rid); } else { onReset(); @@ -233,7 +233,7 @@ const Page: React.FC = (props) => { redirectOption === 'forceHttps' && filterHosts.length !== 0 ? checkHostWithSSL(hosts) : Promise.resolve(), - checkUniqueName(value.name, (props as any).match.params.rid || ''), + checkUniqueName(value.name, props.route.path.indexOf('edit') > 0 ? (props as any).match.params.rid : ''), ]).then(() => { setStep(nextStep); }); @@ -269,9 +269,9 @@ const Page: React.FC = (props) => { return ( <> diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx index dc5c3d37d7..a9c1ba5cc6 100644 --- a/web/src/pages/Route/List.tsx +++ b/web/src/pages/Route/List.tsx @@ -417,6 +417,9 @@ const Page: React.FC = () => { }}> {formatMessage({ id: 'component.global.view' })} + {