mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1376 lines
47 KiB
1376 lines
47 KiB
<!-- eslint-disable --> |
|
<template> |
|
<v-container fluid class="api-client grid-list-xs pa-0" style="height: 100%"> |
|
<splitpanes style="height: 100%" class="xc-theme"> |
|
<pane min-size="20" max-size="50" size="30" style="overflow: auto"> |
|
<!-- <p class="body-2 my-1 text-center grey--text text--lighten-1" v-show="!isDashboard">--> |
|
<!-- {..} REST API CLIENT</p>--> |
|
<v-row class="pa-0 ma-0 pa-2 pl-2"> |
|
<div class="cursor-pointer d-flex" style="width: 100%"> |
|
<x-icon |
|
v-ge="['api-client', 'open-new-collection']" |
|
icon-class="mr-1 cursor-pointer" |
|
small |
|
color="primary" |
|
tooltip="Create New API Collection" |
|
@click="openNewCollection('/')" |
|
> |
|
mdi-folder-plus-outline |
|
</x-icon> |
|
|
|
<!-- <x-icon class="mr-1" color="primary" tooltip="Open API Collection" @click="openApiFileCollection">--> |
|
<!-- mdi-folder-open-outline--> |
|
<!-- </x-icon>--> |
|
<v-dialog v-model="importCollection.modal" persistent max-width="550"> |
|
<template #activator="{ on }"> |
|
<v-icon color="" v-on="on"> mdi-import </v-icon> |
|
</template> |
|
<v-card class="pa-3" style="position: relative"> |
|
<v-icon |
|
v-ge="['api-client', 'open-new-collection-close']" |
|
style="position: absolute; right: 20px; top: 20px" |
|
@click="importCollection.modal = false" |
|
> |
|
mdi-close |
|
</v-icon> |
|
|
|
<v-card-title class="headline justify-center mb-2"> Import </v-card-title> |
|
<v-card-subtitle class="text-center"> (JSON Format Only) </v-card-subtitle> |
|
|
|
<div style="" class="py-3"> |
|
<v-card-text> |
|
<v-select |
|
v-model="importCollection.type" |
|
dense |
|
hide-details |
|
outlined |
|
:items="importCollection.types" |
|
filled |
|
label="API Specification" |
|
/> |
|
</v-card-text> |
|
<v-card-actions> |
|
<v-tabs background-color="indigo accent-4" dark style="border: 1px solid indigo; margin: 0 10px"> |
|
<v-tab>FILE</v-tab> |
|
<v-tab :disabled="!isSwaggerImport"> URL </v-tab> |
|
<v-tab :disabled="!isSwaggerImport"> JSON </v-tab> |
|
<v-tab-item> |
|
<div class="px-2 py-10 text-center" style="min-height: 250px"> |
|
<v-row> |
|
<v-col> |
|
<v-text-field |
|
v-model="importCollection.file.srcFilePath" |
|
hide-details |
|
label="Select file" |
|
dense |
|
outlined |
|
> |
|
<template #append> |
|
<v-btn v-ge="['api-client', 'select-import-file']" small @click="selectImportFile()"> |
|
Choose File ... |
|
</v-btn> |
|
</template> |
|
</v-text-field> |
|
</v-col> |
|
</v-row> |
|
|
|
<v-row v-show="isSwaggerImport"> |
|
<v-col> |
|
<v-text-field |
|
v-model="importCollection.file.dstFilePath" |
|
hide-details |
|
label="Destination file path" |
|
dense |
|
outlined |
|
hide-details |
|
/> |
|
</v-col> |
|
</v-row> |
|
|
|
<v-btn |
|
v-ge="['api-client', 'import-collection']" |
|
class="mt-4" |
|
color="primary" |
|
@click=" |
|
importFile( |
|
importCollection.type, |
|
importCollection.file.srcFilePath, |
|
importCollection.file.dstFilePath |
|
) |
|
" |
|
> |
|
Import |
|
</v-btn> |
|
</div> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<div style="min-height: 250px" class="text-center"> |
|
<div class="px-2 pt-10"> |
|
<v-text-field v-model="importCollection.url" placeholder="Enter url" /> |
|
</div> |
|
|
|
<v-btn |
|
v-ge="['api-client', 'import-collection-from-url']" |
|
color="primary" |
|
@click="importFromUrl(importCollection.type, importCollection.url)" |
|
> |
|
Import |
|
</v-btn> |
|
</div> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<div style="min-height: 250px" class="text-center"> |
|
<div class="px-2 pt-2"> |
|
<v-textarea v-model="importCollection.text" placeholder="Enter JSON String" /> |
|
</div> |
|
<v-btn |
|
v-ge="['api-client', 'import-collection-from-text']" |
|
color="primary" |
|
@click="importFromText(importCollection.type, importCollection.text)" |
|
> |
|
Import |
|
</v-btn> |
|
</div> |
|
</v-tab-item> |
|
</v-tabs> |
|
</v-card-actions> |
|
</div> |
|
</v-card> |
|
</v-dialog> |
|
<v-spacer /> |
|
<span v-show="!isDashboard" class="flex-shrink-1 body-2 my-1 text-center grey--text"> |
|
{..} REST API CLIENT ( is in <v-icon small color="warning">mdi-alpha</v-icon>)</span |
|
> |
|
|
|
<span v-show="isDashboard" class="caption float-right warning--text mt-1" style="font-style: italic"> |
|
API Client is in<v-icon small color="warning">mdi-alpha</v-icon> |
|
</span> |
|
</div> |
|
</v-row> |
|
<v-divider /> |
|
|
|
<v-tabs v-model="apiRootTab" height="32" style="border-top: 1px solid darkgrey"> |
|
<v-tab style="text-transform: none"> History </v-tab> |
|
<v-tab style="text-transform: none"> Collections </v-tab> |
|
|
|
<v-tab-item style="border-top: 1px solid grey"> |
|
<div class="apis-list"> |
|
<div v-for="(api, i) in historyList" :key="i" class="pa-0 ma-0"> |
|
<v-list-item v-ge="['api-client', 'history']" dense two-line @click="apiClickedOnHistoryList(api)"> |
|
<v-hover v-slot="{ hover }"> |
|
<v-list-item-content> |
|
<v-list-item-title class="grey--text"> |
|
<v-btn |
|
class="pl-0 ml-0" |
|
small |
|
text |
|
:tooltip="api.meta.path" |
|
:color="apiMethodMeta[api.meta.method.toUpperCase()].color" |
|
> |
|
<b> |
|
<v-icon |
|
class="mx-0 ml-n2" |
|
:class="{ |
|
'white--text': !api.meta.response || !api.meta.response.status, |
|
'red--text': api.meta.response && api.meta.response.status >= 400, |
|
'green--text': api.meta.response && api.meta.response.status < 400, |
|
}" |
|
> |
|
mdi-circle-small |
|
</v-icon> |
|
{{ api.meta.method }} |
|
</b> |
|
</v-btn> |
|
|
|
{{ api.meta.path }} |
|
</v-list-item-title> |
|
<v-list-item-subtitle v-show="!i || hover" class="text-right"> |
|
<span v-show="!i && !hover" class="grey--text text--darken-1 caption">(Last invoked API) </span> |
|
|
|
<v-icon v-if="_isDev" v-ge="['api-client', 'node-info']" small @click.stop="showNodeInfo(api)"> |
|
mdi-information |
|
</v-icon> |
|
<v-btn |
|
v-show="hover" |
|
v-ge="['api-client', 'delete-list']" |
|
small |
|
text |
|
class=" " |
|
@click="apiDeleteFromList(i)" |
|
> |
|
<v-icon small> mdi-delete </v-icon> |
|
</v-btn> |
|
</v-list-item-subtitle> |
|
</v-list-item-content> |
|
</v-hover> |
|
</v-list-item> |
|
<v-divider /> |
|
</div> |
|
</div> |
|
</v-tab-item> |
|
<v-tab-item style="border-top: 1px solid grey"> |
|
<v-expansion-panels v-model="curApiCollectionPanel" accordion focusable> |
|
<v-expansion-panel v-for="(apiTv, i) in apiTvs" :key="i"> |
|
<v-expansion-panel-header hide-actions> |
|
<template #default="{ open }"> |
|
<div class="d-flex"> |
|
<v-icon color=""> |
|
{{ open ? 'mdi-menu-down' : 'mdi-menu-right' }} |
|
</v-icon> |
|
<v-icon small color="grey" class="ml-1 mr-2"> mdi-folder </v-icon> |
|
|
|
<span class="body-2 flex-grow-1">{{ |
|
$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][i].fileName |
|
}}</span> |
|
|
|
<x-icon |
|
color="white grey" |
|
class="float-right mr-3" |
|
small |
|
@click="(showCtxMenu[i] = true), (x = $event.clientX), (y = $event.clientY)" |
|
@click.stop="" |
|
> |
|
mdi-dots-horizontal |
|
</x-icon> |
|
|
|
<recursive-menu |
|
v-model="showCtxMenu[i]" |
|
v-ge="['api-client', 'collection-context-menu']" |
|
:position-x="x" |
|
:position-y="y" |
|
:items="{ |
|
'Add Folder': 'add-folder', |
|
'Add Request': 'add-request', |
|
'Reveal in Folder': 'reveal-in-folder', |
|
'Delete Collection': 'delete-collection', |
|
'Refresh Collection': 'refresh-collection', |
|
}" |
|
@click="ctxMenuClickHandler($event, i)" |
|
/> |
|
</div> |
|
</template> |
|
</v-expansion-panel-header> |
|
<v-expansion-panel-content class="expansion-wrap-0 pl-4"> |
|
<vue-tree-list |
|
v-if="apiTvs[i]" |
|
style="cursor: pointer" |
|
class="body-2 sql-query-treeview px-1 pt-2 api-treeview" |
|
:model="apiTv" |
|
default-tree-node-name="new node" |
|
default-leaf-node-name="new leaf" |
|
:default-expanded="false" |
|
@click="tvNodeOnClick" |
|
@change-name="tvNodeRename" |
|
@delete-node="tvNodeDelete" |
|
@add-node="onAddNode" |
|
> |
|
<span slot="leafNodeIcon" /> |
|
<v-icon slot="treeNodeIcon" small color="grey" class="mr-1"> mdi-folder-star </v-icon> |
|
|
|
<v-icon slot="addTreeNode" small> mdi-folder-plus </v-icon> |
|
<v-icon slot="addLeafNode" small> mdi-file-plus </v-icon> |
|
<v-icon slot="editNode" small class="mt-n1"> mdi-file-edit </v-icon> |
|
<v-icon slot="delNode" small> mdi-delete </v-icon> |
|
<template #label="{ item: api }"> |
|
<div |
|
v-if="api.isLeaf" |
|
class="d-flex pa-1 ma-n1" |
|
style="width: 100%" |
|
:style=" |
|
api.id === currentApi.id && !currentApi.meta.history ? 'background:rgb(240, 240, 240)' : '' |
|
" |
|
> |
|
<!-- <v-icon class="mx-0"--> |
|
<!-- :class="`${apiMeta[api.method].color}--text`"--> |
|
<!-- small>--> |
|
<!-- mdi-bookmark-outline--> |
|
<!-- </v-icon>--> |
|
<span |
|
style="display: inline-block; min-width: 45px" |
|
:class=" |
|
apiMethodMeta[api.meta.method.toUpperCase()] |
|
? `${apiMethodMeta[api.meta.method.toUpperCase()].color}--text` |
|
: '' |
|
" |
|
> |
|
{{ api.meta.method.toUpperCase() === 'DELETE' ? 'DEL' : api.meta.method.toUpperCase() }} |
|
</span> |
|
|
|
<span |
|
class="grey--text d-block text--darken-1" |
|
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap" |
|
>{{ api.name }}</span |
|
> |
|
<v-spacer /> |
|
<v-icon v-if="_isDev" v-ge="['api-client', 'node-info']" small @click.stop="showNodeInfo(api)"> |
|
mdi-information |
|
</v-icon> |
|
</div> |
|
<div |
|
v-else |
|
class="d-flex" |
|
style="width: 100%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap" |
|
> |
|
<span>{{ api.name }}</span> |
|
|
|
<v-spacer /> |
|
<v-icon v-if="_isDev" v-ge="['api-client', 'node-info']" small @click.stop="showNodeInfo(api)"> |
|
mdi-information |
|
</v-icon> |
|
</div> |
|
</template> |
|
</vue-tree-list> |
|
</v-expansion-panel-content> |
|
</v-expansion-panel> |
|
</v-expansion-panels> |
|
</v-tab-item> |
|
</v-tabs> |
|
</pane> |
|
<pane min-size="10" size="70" style="overflow: auto"> |
|
<v-toolbar |
|
class="toolbar-border-bottom elevation-0 d-flex req-inputs" |
|
height="55" |
|
style="position: relative; z-index: 2; width: 100%" |
|
> |
|
<v-select |
|
v-model="api.method" |
|
v-ge="['api-client', 'method']" |
|
:items="Object.keys(apiMethodMeta)" |
|
dense |
|
solo |
|
hide-details |
|
outlined |
|
class="body-2" |
|
style=" |
|
max-width: 130px; |
|
border-bottom-right-radius: 0; |
|
border-top-right-radius: 0; |
|
border-right: 1px solid grey; |
|
" |
|
/> |
|
|
|
<xAutoComplete |
|
v-model="api.path" |
|
outlined |
|
class="flex-grow-1" |
|
:env="selectedEnv" |
|
placeholder="Enter HTTP URL" |
|
solo |
|
dense |
|
hide-details |
|
autofocus |
|
styles="border-bottom-left-radius: 0;border-top-left-radius:0 " |
|
/> |
|
|
|
<x-btn |
|
v-ge="['api-client', 'api-send']" |
|
btn.class="primary" |
|
dense |
|
tooltip="Send Request" |
|
@click.prevent="apiSend()" |
|
> |
|
<v-icon v-if="isPerfFilled" small class="ml-n3"> mdi-truck-fast </v-icon> |
|
<v-icon v-else small class="ml-n3"> mdi-send </v-icon> |
|
SEND |
|
</x-btn> |
|
<x-btn |
|
v-ge="['api-client', 'save']" |
|
btn.class="outlined" |
|
dense |
|
:icon="api.history ? 'save' : 'mdi-content-save-edit'" |
|
tooltip="Save API" |
|
@click.prevent="saveOrUpdateApi(api)" |
|
/> |
|
<x-btn |
|
v-ge="['api-client', 'environment']" |
|
icon="mdi-eye-outline" |
|
tooltip="Environments" |
|
@click="environmentDialog = true" |
|
/> |
|
</v-toolbar> |
|
|
|
<splitpanes horizontal style="height: calc(100% - 64px)" class="xc-theme"> |
|
<pane min-size="25" size="50" style="overflow: auto" class="pa-1"> |
|
<v-tabs class="req-tabs" height="24"> |
|
<v-tab v-ge="['api-client', 'params']" class="caption"> |
|
Params <b v-if="paramsCount" class="green--text">({{ paramsCount }})</b> |
|
</v-tab> |
|
<v-tab v-ge="['api-client', 'headers']" class="caption"> |
|
Headers <b v-if="headersCount" class="green--text">({{ headersCount }})</b> |
|
</v-tab> |
|
<v-tab v-ge="['api-client', 'body']" class="caption"> Body </v-tab> |
|
<v-tab v-ge="['api-client', 'auth']" class="caption"> Auth </v-tab> |
|
<v-tab v-ge="['api-client', 'perf-test']" class="caption"> Perf Test </v-tab> |
|
<div class="flex-grow-1 d-flex text-right pr-4 justify-end"> |
|
<div class="flex-shrink-1"> |
|
<v-select |
|
v-model="selectedEnv" |
|
height="19" |
|
class="caption envs" |
|
dense |
|
:items="environmentList" |
|
placeholder="Environment" |
|
single-line |
|
> |
|
<template #selection="{ item }"> |
|
<span style="text-transform: uppercase">{{ item }}</span |
|
> <span class="grey--text">(env)</span> |
|
</template> |
|
</v-select> |
|
</div> |
|
</div> |
|
<!-- <div class="flex-grow-1 text-right pr-4 caption" v-if="api.response">--> |
|
<!-- <!– <x-icon iconClass="mr-4" v-if="$store.getters['project/GtrProjectJson']" @click="environmentDialog = true" tooltip="Environments">–>--> |
|
<!-- <!– mdi-eye-outline–>--> |
|
<!-- <!– </x-icon>–>--> |
|
<!-- </div>--> |
|
<v-tab-item> |
|
<params v-model="api.parameters" :env.sync="selectedEnv" /> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<headers v-model="api.headers" :env.sync="selectedEnv" /> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<monaco-json-editor |
|
v-model="api.body" |
|
style="height: 250px" |
|
class="editor card" |
|
theme="vs-dark" |
|
lang="json" |
|
:options="{ validate: true, documentFormattingEdits: true, foldingRanges: true }" |
|
/> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<!-- <monaco-editor--> |
|
<!-- :code.sync="api.auth"--> |
|
<!-- cssStyle="height:250px"></monaco-editor>--> |
|
</v-tab-item> |
|
|
|
<v-tab-item> |
|
<perf-test v-model="api.perf" /> |
|
</v-tab-item> |
|
</v-tabs> |
|
</pane> |
|
<pane min-size="25" size="50" style="overflow: auto" class="pa-1"> |
|
<!-- <h3 class="mb-2 grey--text lighten-1">--> |
|
<!-- Response Body :--> |
|
<!-- <div v-if="api.response">--> |
|
<!-- <span v-if="api.response.status === 200" class="green--text">{{api.response.status}}</span>--> |
|
<!-- <span v-if="api.response.status !== 200" class="red--text">{{api.response.status}}</span>--> |
|
<!-- </div>--> |
|
<!-- </h3>--> |
|
|
|
<v-tabs v-if="api.response" height="24"> |
|
<v-tab v-ge="['api-client', 'response-body']" class="caption"> Body </v-tab> |
|
<v-tab v-ge="['api-client', 'respomse-headers']" class="caption"> |
|
Headers<span v-if="api.response.headers" class="green--text" |
|
>( {{ Object.keys(api.response.headers).length }} )</span |
|
> |
|
</v-tab> |
|
<div v-if="api.response" class="flex-grow-1 text-right pr-4 caption"> |
|
<template v-if="api.response.status"> |
|
<span class="grey--text">Status:</span |
|
><span |
|
:class="{ |
|
'green--text': api.response.status === 200, |
|
'red--text': api.response.status !== 200, |
|
}" |
|
><b>{{ api.response.status }}</b></span |
|
> |
|
</template> |
|
|
|
<template v-if="api.timeTaken"> |
|
<span class="grey--text">Time:</span |
|
><span class="green--text" |
|
><b>{{ api.timeTaken }}ms</b></span |
|
> |
|
</template> |
|
</div> |
|
<v-tab-item> |
|
<pre |
|
v-if="api.response" |
|
class="black pa-1 grey--text w-100 caption" |
|
style="overflow-x: auto; min-height: 100px; overflow-y: auto; min-width: 100%" |
|
>{{ api.response.data }}</pre |
|
> |
|
|
|
<!-- <pre v-if="api.response" class="black pa-1" style="overflow-x: auto;min-height:50px;overflow-y:auto">{{api.response.data}}</pre>--> |
|
</v-tab-item> |
|
<v-tab-item> |
|
<pre |
|
v-if="api.response" |
|
class="black pa-1 grey--text w-100 caption" |
|
style="overflow-x: auto; min-height: 100px; overflow-y: auto; min-width: 100%" |
|
>{{ api.response.headers }}</pre |
|
> |
|
</v-tab-item> |
|
</v-tabs> |
|
</pane> |
|
</splitpanes> |
|
</pane> |
|
</splitpanes> |
|
<environment v-model="environmentDialog" env="_noco" /> |
|
|
|
<v-dialog v-model="bookmarkApiDialog" max-width="500"> |
|
<v-card> |
|
<v-card-title class="justify-center"> Bookmark API </v-card-title> |
|
|
|
<v-card-text> |
|
<div class="text-right"> |
|
<v-btn |
|
v-ge="['api-client', 'open-new-collection']" |
|
small |
|
color="primary" |
|
outlined |
|
@click="openNewCollection('/')" |
|
> |
|
<v-icon small> mdi-plus </v-icon> New Collection |
|
</v-btn> |
|
</div> |
|
<v-list dense> |
|
<v-list-item-group v-model="choosenApiCollection"> |
|
<v-list-item |
|
v-for="(item, i) in $store.getters['apiClientSwagger/GtrCurrentApiFilePaths']" |
|
:key="i" |
|
:value="i" |
|
style="border: 1px solid grey" |
|
> |
|
<v-list-item-title>{{ item.fileName }}</v-list-item-title> |
|
</v-list-item> |
|
</v-list-item-group> |
|
</v-list> |
|
</v-card-text> |
|
<v-card-actions class="justify-center pb-5"> |
|
<v-btn v-ge="['api-client', 'bookmark']" small @click="bookmarkApiDialog = false"> |
|
<!-- Cancel --> |
|
{{ $t('general.cancel') }} |
|
</v-btn> |
|
<v-btn |
|
small |
|
color="primary" |
|
:disabled="choosenApiCollection === null || choosenApiCollection === undefined" |
|
@click="bookmarkApi(choosenApiCollection)" |
|
> |
|
Save API |
|
</v-btn> |
|
</v-card-actions> |
|
</v-card> |
|
</v-dialog> |
|
</v-container> |
|
</template> |
|
<script> |
|
/* eslint-disable */ |
|
|
|
import { mapGetters } from 'vuex'; |
|
|
|
import Vue from 'vue'; |
|
|
|
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'; |
|
import { Splitpanes, Pane } from 'splitpanes'; |
|
import params from '../apiClient/Params'; |
|
import headers from '../apiClient/Headers'; |
|
|
|
import { MonacoJsonEditor } from '../monaco/index'; |
|
|
|
import environment from '../Environment'; |
|
import PerfTest from '../apiClient/PerfTest'; |
|
|
|
// const {app, dialog, path, fs, shell, XcApis} = require("electron").remote.require( |
|
// "./libs" |
|
// ); |
|
|
|
import * as XcApiHelp from '../../helpers/XcApiHelp'; |
|
|
|
// const {config} = require('electron').remote.require('./libs'); |
|
|
|
export default { |
|
components: { |
|
PerfTest, |
|
MonacoJsonEditor, |
|
VueTreeList, |
|
Splitpanes, |
|
Pane, |
|
params, |
|
headers, |
|
environment, |
|
}, |
|
data() { |
|
return { |
|
choosenApiCollection: null, |
|
bookmarkApiDialog: false, |
|
apiRootTab: 0, |
|
importCollection: { |
|
modal: false, |
|
type: 'Swagger 3.0', |
|
types: [ |
|
{ |
|
text: 'Swagger 3.0', |
|
value: 'Swagger 3.0', |
|
}, |
|
{ |
|
text: 'XC', |
|
value: 'XC', |
|
}, |
|
], |
|
file: { |
|
srcFilePath: '', |
|
dstFilePath: '', |
|
}, |
|
}, |
|
fileHistory: null, |
|
historyList: [], |
|
environmentDialog: false, |
|
showCtxMenu: {}, |
|
apiTvs: [], |
|
apiFilePaths: [], |
|
apiFileCollections: [], |
|
curApiCollectionPanel: null, |
|
|
|
x: 0, |
|
y: 0, |
|
|
|
apiMethodMeta: { |
|
GET: { |
|
color: 'success', |
|
}, |
|
POST: { |
|
color: 'warning', |
|
}, |
|
DELETE: { |
|
color: 'error', |
|
}, |
|
PUT: { |
|
color: 'info', |
|
}, |
|
HEAD: { |
|
color: 'info', |
|
}, |
|
PATCH: { |
|
color: 'info', |
|
}, |
|
}, |
|
|
|
// current api |
|
api: { |
|
method: 'GET', |
|
path: '', |
|
body: '', |
|
params: [], |
|
auth: '', |
|
headers: [], |
|
response: {}, |
|
perf: {}, |
|
meta: {}, |
|
}, |
|
currentApi: {}, |
|
currentNode: null, |
|
}; |
|
}, |
|
computed: { |
|
isPerfFilled() { |
|
return this.api.perf && Object.values(this.api.perf).some(v => v); |
|
}, |
|
...mapGetters({ |
|
sqlMgr: 'sqlMgr/sqlMgr', |
|
currentProjectFolder: 'project/currentProjectFolder', |
|
projectApisFolderPath: 'project/projectApisFolderPath', |
|
projectApisFolder: 'project/projectApisFolder', |
|
}), |
|
isSwaggerImport() { |
|
return this.importCollection.type === 'Swagger 3.0'; |
|
}, |
|
paramsCount() { |
|
return this.api.parameters && this.api.parameters.filter(p => p.name && p.enabled).length; |
|
}, |
|
headersCount() { |
|
return this.api.headers && this.api.headers.filter(h => h.name && h.enabled).length; |
|
}, |
|
environmentList() { |
|
if (this.isDashboard) { |
|
return Object.keys(this.$store.getters['project/GtrApiEnvironment']); |
|
} else { |
|
return Object.keys(this.$store.getters['project/GtrDefaultApiEnvironment']); |
|
} |
|
}, |
|
selectedEnv: { |
|
get() { |
|
return ( |
|
this.$store.state.apiClientSwagger.activeEnvironment[this.$store.state.apiClientSwagger.currentProjectKey] || |
|
this.environmentList[0] |
|
); |
|
}, |
|
set(env) { |
|
this.$store.commit('apiClientSwagger/MutActiveEnvironment', { env }); |
|
}, |
|
}, |
|
}, |
|
watch: { |
|
async historyList(newData) { |
|
await this.fileHistory.write({ |
|
path: config.electron.apiHistoryPath, |
|
data: newData, |
|
}); |
|
}, |
|
}, |
|
|
|
async created() { |
|
try { |
|
/* load history collection - default path is within app directory */ |
|
await this.loadHistoryFile(); |
|
|
|
if (this.$route.params && this.$route.params.project) { |
|
/* get collection paths previously opened for this project */ |
|
this.$store.dispatch('apiClientSwagger/loadApiCollectionForProject', { |
|
projectId: this.$route.params.project, |
|
projectName: this.$store.getters['project/GtrProjectName'], |
|
}); |
|
} else { |
|
/* get collection paths previously opened */ |
|
this.$store.dispatch('apiClientSwagger/loadApiCollectionForProject', {}); |
|
} |
|
|
|
/* load the filecollection json as treeviews */ |
|
for (let i = 0; i < this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'].length; ++i) { |
|
await this.loadFileCollection(this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][i]); |
|
} |
|
} catch (e) { |
|
console.log('Failed to load previously opened query collections', e); |
|
} |
|
|
|
if (this.nodes && this.nodes.url) { |
|
Vue.set(this.api, 'method', 'GET'); |
|
this.api.path = this.nodes.url; |
|
} |
|
|
|
this.api.meta = {}; |
|
|
|
try { |
|
const info = ( |
|
await this.$axios.get('/nc/projectApiInfo', { |
|
headers: { |
|
'xc-auth': this.$store.state.users.token, |
|
}, |
|
}) |
|
).data; |
|
const rest = Object.values(info).find(v => v.apiUrl); |
|
if (rest) { |
|
Vue.set(this.api, 'method', 'GET'); |
|
Vue.set(this.api, 'path', rest.apiUrl); |
|
} |
|
} catch (e) {} |
|
}, |
|
mounted() {}, |
|
beforeDestroy() {}, |
|
methods: { |
|
showNodeInfo(api) { |
|
console.log('Node info : ', api); |
|
}, |
|
async handleKeyDown({ metaKey, key, altKey, shiftKey, ctrlKey }) { |
|
// cmd + s -> save |
|
// cmd + l -> reload |
|
// cmd + n -> new |
|
// cmd + d -> delete |
|
// cmd + enter -> send api |
|
|
|
switch ([metaKey, key].join('_')) { |
|
case 'true_s': |
|
await this.bookmarkApi(); |
|
break; |
|
case 'true_e': |
|
this.environmentDialog = true; |
|
break; |
|
// case 'true_n' : |
|
// this.addColumn(); |
|
// break; |
|
case 'true_d': |
|
await this.deleteProcedure('showDialog'); |
|
break; |
|
case 'true_Enter': |
|
await this.apiSend(); |
|
break; |
|
} |
|
}, |
|
|
|
async openCollectionFolder(pathString) { |
|
shell.showItemInFolder(pathString); |
|
}, |
|
async openNewCollection() { |
|
try { |
|
const toLocalPath = this.isDashboard |
|
? path.join(this.currentProjectFolder, 'server', 'tool', this.projectApisFolder) |
|
: ''; |
|
|
|
const userChosenPath = dialog.showSaveDialog({ |
|
defaultPath: toLocalPath, |
|
filters: [{ name: 'JSON', extensions: ['json'] }], |
|
}); |
|
|
|
if (userChosenPath) { |
|
fs.writeFileSync(userChosenPath, '[]', 'utf-8'); |
|
const pathObj = { |
|
path: userChosenPath, |
|
fileName: path.basename(userChosenPath), |
|
}; |
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
} |
|
this.$toast.success('New API collection loaded successfully').goAway(5000); |
|
} catch (e) { |
|
console.log(e); |
|
throw e; |
|
} |
|
}, |
|
async openSwaggerJSONFile(srcPath, dstPath) { |
|
const dstFileName = path.basename(dstPath); |
|
await XC.importSwaggerJson({ |
|
srcPath, |
|
dstPath, |
|
}); |
|
const pathObj = { |
|
path: dstPath, |
|
fileName: dstFileName, |
|
}; |
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
}, |
|
selectImportFile() { |
|
const file = dialog.showOpenDialog({ |
|
properties: ['openFile'], |
|
}); |
|
|
|
const dstFileName = `${path.basename(file[0], '.json')}.xc.json`; |
|
|
|
let dstPath = ''; |
|
if (this.isDashboard) { |
|
dstPath = this.projectApisFolderPath; |
|
} else { |
|
dstPath = path.dirname(config.electron.apiHistoryPath); |
|
} |
|
|
|
if (file && file[0]) { |
|
this.$set(this.importCollection.file, 'srcFilePath', file[0] + ''); |
|
this.$set(this.importCollection.file, 'dstFilePath', path.join(dstPath, dstFileName)); |
|
} |
|
}, |
|
async importFile(type, srcPath, dstPath) { |
|
this.apiRootTab = 1; |
|
this.$set(this.importCollection, 'modal', false); |
|
try { |
|
if (type === 'Swagger 3.0') { |
|
await this.openSwaggerJSONFile(srcPath, dstPath); |
|
} else { |
|
await this.openApiFileCollection(srcPath, dstPath); |
|
} |
|
this.$toast.success('File imported successfully').goAway(3000); |
|
} catch (e) { |
|
console.log('File import error : ', e); |
|
this.$toast.error('File importing failed').goAway(3000); |
|
} |
|
}, |
|
async importFromUrl(type, url) { |
|
this.apiRootTab = 1; |
|
this.$set(this.importCollection, 'modal', false); |
|
try { |
|
if (type === 'Swagger 3.0') { |
|
await this.importSwaggerFromUrl(url); |
|
} else { |
|
await this.importXcFromUrl(url); |
|
} |
|
this.$toast.success('Import from URL completed successfully').goAway(3000); |
|
} catch (e) { |
|
console.log('URL import error : ', e); |
|
this.$toast.error('Import from URL failed').goAway(3000); |
|
} |
|
}, |
|
async importFromText(type, text) { |
|
this.apiRootTab = 1; |
|
this.$set(this.importCollection, 'modal', false); |
|
try { |
|
if (type === 'Swagger 3.0') { |
|
await this.importSwaggerFromText(text); |
|
} else { |
|
await this.importXcFromText(text); |
|
} |
|
this.$toast.success('Import from Text completed successfully').goAway(3000); |
|
} catch (e) { |
|
console.log('Text import error : ', e); |
|
this.$toast.error('Import from Text failed').goAway(3000); |
|
} |
|
}, |
|
|
|
async importSwaggerFromUrl(url) { |
|
const data = await this.$axios.$get(url); |
|
|
|
const dstFileName = `${Date.now()}.xc.json`; |
|
|
|
let dstPath = ''; |
|
if (this.isDashboard) { |
|
dstPath = this.projectApisFolderPath; |
|
} else { |
|
dstPath = path.dirname(config.electron.apiHistoryPath); |
|
} |
|
|
|
await XC.importSwaggerObject({ |
|
swagger: data, |
|
dstPath: path.join(dstPath, dstFileName), |
|
}); |
|
|
|
const pathObj = { |
|
path: path.join(dstPath, dstFileName), |
|
fileName: dstFileName, |
|
}; |
|
|
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
}, |
|
async importSwaggerFromText(text) { |
|
const data = JSON.parse(text); |
|
|
|
const dstFileName = `${Date.now()}.xc.json`; |
|
|
|
let dstPath = ''; |
|
if (this.isDashboard) { |
|
dstPath = this.projectApisFolderPath; |
|
} else { |
|
dstPath = path.dirname(config.electron.apiHistoryPath); |
|
} |
|
|
|
await XC.importSwaggerObject({ |
|
swagger: data, |
|
dstPath: path.join(dstPath, dstFileName), |
|
}); |
|
|
|
const pathObj = { |
|
path: path.join(dstPath, dstFileName), |
|
fileName: dstFileName, |
|
}; |
|
|
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
|
|
this.$toast.success('Import from Text completed successfully').goAway(3000); |
|
}, |
|
async importXcFromUrl(url) { |
|
const data = await this.$axios.$get(url); |
|
|
|
const dstFileName = `${Date.now()}.xc.json`; |
|
|
|
let dstPath = ''; |
|
if (this.isDashboard) { |
|
dstPath = this.projectApisFolderPath; |
|
} else { |
|
dstPath = path.dirname(config.electron.apiHistoryPath); |
|
} |
|
|
|
// await XcApis.importSwaggerObject({ |
|
// swagger: data, |
|
// dstPath: path.join(dstPath, dstFileName) |
|
// }) |
|
|
|
const pathObj = { |
|
path: path.join(dstPath, dstFileName), |
|
fileName: dstFileName, |
|
}; |
|
|
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
}, |
|
async importXcFromText(text) { |
|
const data = JSON.parse(text); |
|
|
|
const dstFileName = `${Date.now()}.xc.json`; |
|
|
|
let dstPath = ''; |
|
if (this.isDashboard) { |
|
dstPath = this.projectApisFolderPath; |
|
} else { |
|
dstPath = path.dirname(config.electron.apiHistoryPath); |
|
} |
|
// |
|
// await XcApis.importSwaggerObject({ |
|
// swagger: data, |
|
// dstPath: path.join(dstPath, dstFileName) |
|
// }) |
|
|
|
const pathObj = { |
|
path: path.join(dstPath, dstFileName), |
|
fileName: dstFileName, |
|
}; |
|
|
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
await this.loadFileCollection(pathObj); |
|
|
|
this.$toast.success('Import from Text completed successfully').goAway(3000); |
|
}, |
|
|
|
async ctxMenuClickHandler(actionEvent, index) { |
|
switch (actionEvent.value) { |
|
case 'add-folder': |
|
this.tvNodeFolderAdd(index); |
|
break; |
|
case 'add-request': |
|
this.curApiCollectionPanel = index; |
|
this.tvNodeRequestAdd(index); |
|
break; |
|
case 'reveal-in-folder': |
|
await this.openCollectionFolder(this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][index].path); |
|
break; |
|
case 'delete-collection': |
|
this.$store.commit('apiClientSwagger/MutApiFilePathsRemove', index); |
|
this.apiFileCollections.splice(index, 1); |
|
this.apiTvs.splice(index, 1); |
|
break; |
|
case 'refresh-collection': |
|
await this.refreshFileCollection(index); |
|
break; |
|
default: |
|
break; |
|
} |
|
// this.deleteQueryByPath(this.apiTvs, this.menuItem.path); |
|
}, |
|
openUrl(url) { |
|
shell.openExternal(url); |
|
}, |
|
|
|
apiClickedOnList(api) { |
|
if (api.isLeaf) { |
|
api = XcApiHelp.apiPrepareForInvocation(api); |
|
|
|
this.currentApi = JSON.parse(JSON.stringify(api)); |
|
this.api = { |
|
perf: {}, |
|
...this.currentApi.meta, |
|
}; |
|
} |
|
}, |
|
|
|
apiClickedOnHistoryList(api) { |
|
this.currentApi = JSON.parse(JSON.stringify(api)); |
|
this.api = { |
|
perf: {}, |
|
...api.meta, |
|
history: true, |
|
isLeaf: true, |
|
}; |
|
}, |
|
|
|
apiDeleteFromList(index) { |
|
this.$store.commit('apiClientSwagger/MutListRemove', index); |
|
}, |
|
|
|
async apiSend() { |
|
if (!this.api.path.trim()) { |
|
this.$toast.info('Please enter http url').goAway(3000); |
|
return; |
|
} |
|
|
|
const envs = this.isDashboard |
|
? this.$store.getters['project/GtrApiEnvironment'][this.selectedEnv] |
|
: this.$store.getters['project/GtrDefaultApiEnvironment'][this.selectedEnv]; |
|
|
|
const apiDecoded = JSON.parse( |
|
JSON.stringify(this.api).replace(/{{\s*(\w+)\s*}}/g, (m, m1) => { |
|
if (m1 in envs) { |
|
return envs[m1]; |
|
} else { |
|
this.$toast.info('Environment variable is not found : ' + m1).goAway(3000); |
|
return m; |
|
} |
|
}) |
|
); |
|
|
|
const swaggerResult = await this.$store.dispatch('apiClientSwagger/send', { api: this.api, apiDecoded }); |
|
|
|
this.historyList = [{ ...this.currentApi, meta: swaggerResult, pid: 0 }, ...this.historyList]; |
|
|
|
const result = swaggerResult.response; |
|
this.api = swaggerResult; |
|
|
|
if (result) { |
|
if (result.status === 200) { |
|
// this.$toast.success('API invoked successfully',{duration:1000}); |
|
} else { |
|
this.$toast.error(`Error:${result.status}`, { duration: 1000 }); |
|
} |
|
} else { |
|
this.$toast.error('Some internal error occurred', { duration: 1000 }); |
|
} |
|
}, |
|
|
|
async fileCollectionReload() { |
|
const data = new Tree(await this.apiFileCollection.read()); |
|
|
|
this.apiTv = data; |
|
// this.$set(this, 'apiCollections', data); |
|
}, |
|
|
|
async tvNodeDelete(node) { |
|
console.log(node, this.curApiCollectionPanel); |
|
node.remove(); |
|
await this.savefileCollections(this.curApiCollectionPanel); |
|
}, |
|
|
|
async tvNodeRename(params) { |
|
await this.savefileCollections(this.curApiCollectionPanel); |
|
}, |
|
|
|
async onAddNode(params) { |
|
await this.savefileCollections(this.curApiCollectionPanel); |
|
}, |
|
|
|
async tvNodeOnClick(node) { |
|
const { parent, children, ...params } = node; |
|
this.currentNode = node; |
|
|
|
this.apiClickedOnList(params); |
|
// if (params.query) ; |
|
// this.selectQuery(params) |
|
}, |
|
|
|
async savefileCollections(index = 0) { |
|
await this.apiFileCollections[index].write({ data: this.tvToObject(index) }); |
|
// this.apiTvs = await this.fileCollections.read(); |
|
// this.$set(this.apiTvs, index, this.apiTvs); |
|
}, |
|
|
|
tvToObject(index) { |
|
try { |
|
const vm = this; |
|
|
|
function _dfs(oldNode) { |
|
const newNode = {}; |
|
|
|
XcApiHelp.nodeHandleIfNew(oldNode); |
|
|
|
for (const k in oldNode) { |
|
if (k !== 'children' && k !== 'parent') { |
|
newNode[k] = oldNode[k]; |
|
} |
|
} |
|
|
|
if (oldNode.children && oldNode.children.length > 0) { |
|
newNode.children = []; |
|
for (let i = 0, len = oldNode.children.length; i < len; i++) { |
|
newNode.children.push(_dfs(oldNode.children[i])); |
|
} |
|
} |
|
|
|
return newNode; |
|
} |
|
|
|
return _dfs(vm.apiTvs[index]).children; |
|
} catch (e) { |
|
this.$toast.error('Error while parsing api collection').goAway(3000); |
|
} |
|
}, |
|
|
|
async tvNodeFolderAdd(index) { |
|
const node = new TreeNode({ name: 'New Folder', isLeaf: false, children: [] }); |
|
if (!this.apiTvs[index].children) { |
|
this.apiTvs[index].children = []; |
|
} |
|
this.apiTvs[index].addChildren(node); |
|
|
|
await this.saveFileCollection(index); |
|
}, |
|
async tvNodeRequestAdd(index) { |
|
const node = new TreeNode({ name: 'New Request', isLeaf: true }); |
|
if (!this.apiTvs[index].children) { |
|
this.apiTvs[index].children = []; |
|
} |
|
this.apiTvs[index].addChildren(node); |
|
|
|
await this.saveFileCollection(index); |
|
}, |
|
|
|
openApiFileCollection(filePath = null) { |
|
const vm = this; |
|
// console.log(obj, key); |
|
|
|
const file = filePath |
|
? [filePath] |
|
: dialog.showOpenDialog({ |
|
properties: ['openFile'], |
|
}); |
|
|
|
if (file && file[0]) { |
|
const fileName = path.basename(file[0]); |
|
const pathObj = { |
|
path: file[0] + '', |
|
fileName, |
|
}; |
|
|
|
if (this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'].every(({ path: p }) => p !== pathObj.path)) { |
|
this.$store.commit('apiClientSwagger/MutApiFilePathsAdd', pathObj); |
|
this.loadFileCollection(pathObj); |
|
} else { |
|
this.$toast.info('File already exist in collection').goAway(4000); |
|
} |
|
} |
|
}, |
|
|
|
async saveFileCollection(index = 0) { |
|
await this.apiFileCollections[index].write({ data: this.tvToObject(index) }); |
|
}, |
|
|
|
async loadFileCollection(path, index = 0) { |
|
try { |
|
/* create file collection and get its data */ |
|
const fileCollection = new XC(path); |
|
await fileCollection.init(); |
|
this.apiFileCollections.push(fileCollection); |
|
const treeData = await fileCollection.read(); |
|
|
|
/* create a tree view and push it */ |
|
const data = new Tree(treeData.length && treeData[0].name === 'swagger.json' ? treeData[0].children : treeData); |
|
this.apiTvs.push(data); |
|
|
|
this.curApiCollectionPanel = this.apiFileCollections.length - 1; |
|
} catch (e) { |
|
console.log('Error in loadFileCollection:', e); |
|
throw e; |
|
} |
|
}, |
|
async loadHistoryFile() { |
|
// try { |
|
// this.fileHistory = new XcApis({path: config.electron.apiHistoryPath}); |
|
// this.historyList = await this.fileHistory.read(); |
|
// } catch (e) { |
|
// console.log('Error in loadFileHistory:', e); |
|
// throw e; |
|
// } |
|
}, |
|
async refreshFileCollection(index = 0) { |
|
try { |
|
const path = this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][index]; |
|
const fileCollection = new XC(path); |
|
await fileCollection.init(); |
|
this.apiFileCollections[index] = fileCollection; |
|
const data = new Tree(await fileCollection.read()); |
|
this.$set(this.apiTvs, index, data); |
|
} catch (e) { |
|
console.log('Error in loadFileCollection:', e); |
|
throw e; |
|
} |
|
}, |
|
|
|
async bookmarkApi(panelIndex) { |
|
this.bookmarkApiDialog = false; |
|
if (this.apiFileCollections.length) { |
|
delete this.currentApi.meta.history; |
|
const q = this.currentApi; |
|
const node = new TreeNode({ |
|
name: q.meta.path, |
|
swagger: {}, |
|
isLeaf: true, |
|
id: Date.now(), |
|
pid: 0, |
|
...q, |
|
}); |
|
|
|
if (!this.apiTvs[panelIndex].children) { |
|
this.apiTvs[panelIndex].children = []; |
|
} |
|
this.apiTvs[panelIndex].addChildren(node); |
|
await this.savefileCollections(panelIndex); |
|
await this.refreshFileCollection(panelIndex); |
|
|
|
this.$toast |
|
.success( |
|
`API added to ${this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][panelIndex].fileName} collection successfully` |
|
) |
|
.goAway(3000); |
|
} else { |
|
this.$toast.info('Create a new collection to bookmark the api.').goAway(3000); |
|
this.openNewCollection('/'); |
|
} |
|
}, |
|
async saveOrUpdateApi() { |
|
if (this.api.history) { |
|
this.bookmarkApiDialog = true; |
|
} else { |
|
const q = this.currentApi; |
|
const api = { |
|
...q, |
|
meta: { ...this.api }, |
|
pid: 0, |
|
}; |
|
const activePanelIndex = this.curApiCollectionPanel; |
|
await this.apiFileCollections[activePanelIndex].updateApi({ api }); |
|
await this.refreshFileCollection(activePanelIndex); |
|
this.$toast |
|
.success( |
|
`API updated in ${this.$store.getters['apiClientSwagger/GtrCurrentApiFilePaths'][activePanelIndex].fileName} collection successfully` |
|
) |
|
.goAway(3000); |
|
} |
|
}, |
|
}, |
|
|
|
beforeCreated() {}, |
|
destroy() {}, |
|
directives: {}, |
|
validate({ params }) { |
|
return true; |
|
}, |
|
head() { |
|
return {}; |
|
}, |
|
props: ['nodes'], |
|
}; |
|
</script> |
|
|
|
<style scoped> |
|
.apis-list { |
|
height: calc(100%); |
|
overflow-y: auto; |
|
} |
|
|
|
.apis-request { |
|
overflow-y: auto; |
|
} |
|
|
|
/deep/ > .req-inputs .v-toolbar__content { |
|
width: 100%; |
|
display: flex; |
|
padding: 2px; |
|
} |
|
|
|
/deep/ .req-tabs > .v-tabs-items { |
|
border-top: 1px solid #7f828b33; |
|
} |
|
|
|
.envs /deep/ .v-select__selections { |
|
color: var(--v-accent-base); |
|
} |
|
|
|
/*.envs /deep/ .v-select__selections input { display: none}*/ |
|
code { |
|
min-height: 200px; |
|
} |
|
</style> |
|
|
|
<!-- |
|
/** |
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
|
* |
|
* @author Naveen MR <oof1lab@gmail.com> |
|
* @author Pranav C Balan <pranavxc@gmail.com> |
|
* |
|
* @license GNU AGPL version 3 or any later version |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero General Public License as |
|
* published by the Free Software Foundation, either version 3 of the |
|
* License, or (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
*/ |
|
-->
|
|
|