diff --git a/frontend/src/api/flow.js b/frontend/src/api/flow.js index 46219722a..72acf0fd8 100644 --- a/frontend/src/api/flow.js +++ b/frontend/src/api/flow.js @@ -7,6 +7,12 @@ export const getFlowDetail = (flowId) => { }) } +export const getFlowDetailOrigin = (flowId) => { + return axios({ + url: '/api/flow/' + flowId + "?is_origin=true" + }) +} + export const getFlowList = () => { return axios({ url: '/api/flow' diff --git a/frontend/src/store/inspector.js b/frontend/src/store/inspector.js index 8177feb80..7b0360c16 100644 --- a/frontend/src/store/inspector.js +++ b/frontend/src/store/inspector.js @@ -1,5 +1,6 @@ import * as api from '@/api' import { bus } from '@/eventbus' +import {generateCurl} from '@/utils' export default { state: { @@ -75,6 +76,10 @@ export default { return } state.selectedFlowFilter = selectedFlowFilterName + }, + generateAndCopyCurl(state, requestData){ + let cmd = generateCurl(requestData) + bus.$emit('clipboard', cmd) } }, actions: { @@ -188,6 +193,15 @@ export default { .catch(error => { bus.$emit('msg.error', 'Delete flow error: ' + error.data.message) }) + }, + getFlowDetailForCmd ({state, commit}, flowId) { + api.getFlowDetailOrigin(flowId) + .then(response => { + commit('generateAndCopyCurl', response.data.data.request) + }) + .catch(error => { + bus.$emit('msg.error', 'Get flow detail error: ' + error.data.message) + }) } } } diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 496660be5..7235c4a7e 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,3 +1,5 @@ +import { bus } from '@/eventbus' + export const readablizeBytes = (bytes) => { if(bytes===0){ return '0 B' @@ -31,3 +33,64 @@ export const timestampToDatetime = (timeStamp) => { let second = (dateObj.getSeconds() < 10 ? '0'+dateObj.getSeconds() : dateObj.getSeconds()) return month + '/' + date + ' ' + hour + ':' + minute + ':' + second } + +export const generateCurl = (requestData) => { + let contentType = requestData['headers']['Content-Type'] || '' + let curl = ['curl ' + generateCurlUrl(requestData['url'])] + curl = curl.concat(generateCurlMethod(requestData['method'])) + curl = curl.concat(generateCurlHeader(requestData['headers'])) + curl = curl.concat(generateCurlData(requestData['data'], contentType)) + return curl.join(' \\\n ') +} + +function generateCurlUrl (url) { + return `-g \"${url}\"` +} + +function generateCurlMethod (method) { + return ['-X '+method] +} + +function generateCurlHeader (headers) { + let ignoreKey = [ + 'Host', + 'Accept-Encoding' + ] + let headerStrList = [] + for(let key in headers){ + if(!ignoreKey.includes(key)) + headerStrList.push(`-H \"${key}:${headers[key]}\"`) + } + return headerStrList +} + +function generateCurlData (data, dataType) { + if(typeof data === 'undefined' || !data){ + return [] + } + let dataStrList = [] + if(!dataType){ + dataStrList.push(`--data-raw \'${generateJsonString(data)}\'`) + }else if(dataType.includes('application/json')){ + dataStrList.push(`-d \'${generateJsonString(data)}\'`) + }else if(dataType.includes('application/x-www-form-urlencoded')){ + dataStrList = Object.entries(data).map(([key, value]) => `-d \"${key}=${data[key]}\"`) + }else{ + bus.$emit('msg.error', `Generate curl param -d failed: Lyrebird doesn't support ContentType of ${dataType}`) + } + return dataStrList +} + +function generateJsonString (data) { + let res = '' + if(typeof data === 'string'){ + return data.trim() + }else{ + try{ + res = JSON.stringify(data) + }catch(e){ + bus.$emit('msg.error', `Generate curl param -d failed: Data type conversion failed`) + } + } + return res +} \ No newline at end of file diff --git a/frontend/src/views/Main.vue b/frontend/src/views/Main.vue index 5b2600618..73a3ac2c6 100644 --- a/frontend/src/views/Main.vue +++ b/frontend/src/views/Main.vue @@ -87,6 +87,7 @@ export default { this.$bus.$off('msg.info', this.infoMessage) this.$bus.$off('msg.error', this.errorMessage) this.$bus.$off('msg.destroy', this.destroyMessage) + this.$bus.$off('clipboard', this.copyToClipboard) }, created() { this.$bus.$on('msg.success', this.successMessage) @@ -94,6 +95,7 @@ export default { this.$bus.$on('msg.info', this.infoMessage) this.$bus.$on('msg.error', this.errorMessage) this.$bus.$on('msg.destroy', this.destroyMessage) + this.$bus.$on('clipboard', this.copyToClipboard) this.$io.on('statusBarUpdate', this.loadAllStatusList) this.$io.on('datamanagerUpdate', this.loadDataMap) this.$io.on('msgSuccess', this.successMessage) @@ -198,6 +200,13 @@ export default { destroyMessage() { this.$Message.destroy() }, + copyToClipboard(text) { + this.$copyText(text).then(e => { + this.$bus.$emit('msg.success', 'Copy Success') + }, e => { + this.$bus.$emit('msg.error', 'Copy Failed') + }) + } }, } diff --git a/frontend/src/views/inspector/FlowList.vue b/frontend/src/views/inspector/FlowList.vue index f65136075..a2463f7a2 100644 --- a/frontend/src/views/inspector/FlowList.vue +++ b/frontend/src/views/inspector/FlowList.vue @@ -117,6 +117,27 @@ + + + + Copy as cURL + + + - Copy + Copy Url @@ -380,6 +401,9 @@ export default { } return row.response.mock }, + generateCurlUrl(item){ + this.$store.dispatch('getFlowDetailForCmd', item.id) + }, onUrlCopy () { this.$bus.$emit('msg.success', 'URL copied!') }, @@ -493,7 +517,7 @@ export default { display: inline-block; word-break: keep-all; max-width: 900px; - width: calc(100% - 50px); + width: calc(100% - 100px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/lyrebird/mock/blueprints/apis/flow.py b/lyrebird/mock/blueprints/apis/flow.py index c8c649d44..4ded1b374 100644 --- a/lyrebird/mock/blueprints/apis/flow.py +++ b/lyrebird/mock/blueprints/apis/flow.py @@ -17,11 +17,15 @@ class Flow(Resource): def get(self, id): is_decode = request.args.get('is_decode', 'false').strip().lower() == 'true' + is_origin = request.args.get('is_origin', 'false').strip().lower() == 'true' for item in context.application.cache.items(): if item['id'] == id: # Import decoder for decoding the requested content display_item = {} - application.encoders_decoders.decoder_handler(item, output=display_item) + if is_origin: + display_item.update(item) + else: + application.encoders_decoders.decoder_handler(item, output=display_item) if is_decode: display_item['request'] = deepcopy(display_item['request']) for key in ('url', 'path', 'query'):