/
scicommunity
/
seaf-archtool-core
Обзор
Документация
Войти
/
scicommunity
/
seaf-archtool-core
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
plugins/devtool/components/DevTool.vue
336 строк
11 KB
Alexander Romashin
Запрос на слияние 'feature/ERA-955-gigachat-rag-with-jsonata-query' (#189) из feature/ERA-955-gigachat-rag-with-jsonata-query в dev
16 июн 2025, 11:37
16 июн 2025, 11:37
c7ef04c
Код
Авторство
О чём код?
<!-- Copyright (C) 2023 Sber Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Maintainers: Александр Трубников <a.trubnikov@samolet.ru> Contributors: Александр Трубников <a.trubnikov@samolet.ru> - 2023 Saveliy Zaznobin <zaznobins@yandex.ru>, Sber - 2024 --> <template> <v-container class="desk" grid-list-xl fluid> <v-toolbar dense flat> <v-spacer /> <v-btn v-show="!autoExec" icon title="Выполнить" v-on:click="manualExec(true)"> <v-icon>mdi-arrow-right-drop-circle</v-icon> </v-btn> <v-btn icon title="Добавить панель" v-on:click="addTab()"> <v-icon>mdi-plus</v-icon> </v-btn> <v-menu offset-y> <template #activator="{ on, attrs }"> <v-btn icon v-bind="attrs" v-on="on"> <v-icon>mdi-dots-vertical</v-icon> </v-btn> </template> <v-list> <v-list-item> <v-checkbox v-model="autoExec" /> <v-list-item-title>Автовыполнение</v-list-item-title> </v-list-item> <v-list-item> <v-checkbox v-model="autoExpand" /> <v-list-item-title>Не сворачивать ответ</v-list-item-title> </v-list-item> </v-list> </v-menu> <v-autocomplete v-bind:value="currentOrigins" multiple hide-details clearable v-bind:items="origins" label="origin" title="Базовый источник данных" item-text="id" item-value="id" prepend-icon="mdi-semantic-web" single-line v-on:change="onOriginChange"> <template #selection="data"> <v-chip close v-bind="data.attrs" v-bind:input-value="data.selected" v-on:click:close="() => deleteOrigin(data.item.id)"> {{ data.item.id }} </v-chip> </template> </v-autocomplete> <template #extension> <v-tabs v-model="selectedTab" show-arrows> <v-tab v-for="(tab, index) in tabs" v-bind:key="tab.id" class="tab"> <v-btn icon small title="Клонировать панель" class="btn-copy" v-on:click="cloneTab(index)"> <v-icon x-small>mdi-content-copy</v-icon> </v-btn> {{ tab.name }} <v-btn v-if="tabs.length >= 2" icon small title="Удалить панель" class="btn-del" v-on:click="delTab(index)"> <v-icon x-small>mdi-close</v-icon> </v-btn> </v-tab> </v-tabs> </template> </v-toolbar> <split v-if="tabs.length" v-bind:direction="'vertical'"> <split-area v-bind:size="40" class="area-space"> <!-- TODO: минимальная высота на всю split-area или передача клика в input--> <!-- TODO: theme selector, но лучше просто light/dark--> <code-component v-if="tabs[selectedTab]" v-model="tabs[selectedTab].code" v-bind:change="onChange" /> </split-area> <split-area v-bind:size="60" class="area-space"> <div v-if="tabs[selectedTab]" class="response"> <!-- TODO: loader здорового человека --> <div v-if="tabs[selectedTab].loading">Идет загрузка</div> <div v-else> <div v-if="tabs[selectedTab].emptyData"> {{ tabs[selectedTab].emptyData }} </div> <div v-else-if="tabs[selectedTab].unexpectedError"> {{ tabs[selectedTab].unexpectedError }} </div> <div v-else-if="tabs[selectedTab].error"> <response-component v-if="tabs[selectedTab].error" v-bind:data="tabs[selectedTab].error" v-bind:auto-expand="autoExpand" /> </div> <div v-else> <response-component v-if="tabs[selectedTab].response" v-bind:data="tabs[selectedTab].response" v-bind:auto-expand="autoExpand" /> </div> </div> </div> </split-area> </split> </v-container> </template> <script> import cookie from 'vue-cookie'; import CodeComponent from './CodeComponent.vue'; import ResponseComponent from './ResponseComponent.vue'; import env from '@front/helpers/env'; function uuidv4() { return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); } const COOKIE_NAME_AUTOEXEC = 'json-dev-tool-new-autoexec'; const COOKIE_NAME_AUTOEXPAND = 'json-dev-tool-new-autoexpand'; const LOCALSTORAGE_NAME_TABS = 'json-dev-tool-new-tabs'; const COOKIE_NAME_SELECTEDTAB = 'json-dev-tool-new-selectedtab'; const TAB_DEFAULT = { 'origins': [], 'response': {}, 'loading': false, 'error': null, 'unexpectedError': null, 'controller': null, 'emptyData': null}; export default { name: 'DevTool', components: {CodeComponent, ResponseComponent}, props: { pullData: { type: Function, required: true } }, data() { return { selectedTab: parseInt(cookie.get(COOKIE_NAME_SELECTEDTAB) || 0), tabsCounter: 0, tabs: [], autoExpand: cookie.get(COOKIE_NAME_AUTOEXPAND) === 'true' ? true : false, autoExec: cookie.get(COOKIE_NAME_AUTOEXEC) === 'false' ? false : true, origins: [], debounceTimeout: null }; }, computed: { currentOrigins() { return this.tabs[this.selectedTab]?.origins ?? []; } }, watch: { autoExec(value) { value && this.exec(); cookie.set(COOKIE_NAME_AUTOEXEC, value, 365); }, autoExpand(value) { value && this.exec(); // TODO: тут не нужен перезапрос, но без него не работает cookie.set(COOKIE_NAME_AUTOEXPAND, value, 365); }, selectedTab(value) { cookie.set(COOKIE_NAME_SELECTEDTAB, value, 365); if (this.autoExec){ this.exec(); } } }, mounted() { this.onRefresh(); }, methods: { doRefresh() { if (localStorage.getItem(LOCALSTORAGE_NAME_TABS)) { try { const tabs = JSON.parse(localStorage.getItem(LOCALSTORAGE_NAME_TABS)); this.tabs = tabs.map(tab => ({ ...TAB_DEFAULT, ...tab })); this.tabsCounter = this.tabs.length; this.exec(); return; } catch(e) { localStorage.removeItem(LOCALSTORAGE_NAME_TABS); } } this.addTab(); }, onRefresh() { this.refreshOrigins(); if (this.refresher) clearTimeout(this.refresher); this.refresher = setTimeout(this.doRefresh, 50); }, refreshOrigins() { this.pullData(`(datasets.$spread().{ "id": $keys()[0], "title": *.title })`).then((response) => this.origins = response); }, deleteOrigin(originId) { this.tabs[this.selectedTab].origins = this.tabs[this.selectedTab]?.origins.filter((id) => id !== originId) ?? []; this.manualAutoExec(true); }, saveTabsToLocalStorage(){ const tabs = this.tabs.map(tab => ({ 'id': tab.id, 'name': tab.name, 'code': tab.code, 'origins': tab.origins })); localStorage.setItem(LOCALSTORAGE_NAME_TABS, JSON.stringify(tabs)); }, addTab() { const id = uuidv4(); this.tabsCounter += 1; // кол-во ключей в объекте tabs не получится использовать, т.к. при удалении-создании начнётся каша с нумерацией this.tabs.push({'id': id, 'name': `Панель #${this.tabsCounter}`, 'code': '', ...TAB_DEFAULT}); this.saveTabsToLocalStorage(); }, cloneTab(id) { const oldTab = this.tabs[id]; this.addTab(); this.tabs[this.tabs.length - 1].code = oldTab.code; this.tabs[this.tabs.length - 1].origins = [...oldTab.origins]; }, delTab(id) { this.tabs.splice(id, 1); this.saveTabsToLocalStorage(); }, showEmpty(tab, data) { tab.emptyData = data || 'Пустой ответ от сервера'; }, showSuccess(tab, data) { tab.response = data; }, showError(tab, data) { tab.error = data; }, manualExec(){ this.exec(); }, manualAutoExec(now) { if (this.autoExec) { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { this.exec(); }, now ? 0 : 500); } }, exec(){ const currentTab = this.tabs[this.selectedTab]; currentTab.error = null; currentTab.unexpectedError = null; currentTab.emptyData = null; currentTab.response = null; this.saveTabsToLocalStorage(); if (!currentTab.code){ this.showSuccess(currentTab, ''); return; } this.baseExec(currentTab); }, async baseExec(currentTab){ const origins = currentTab.origins.length > 1 ? currentTab.origins.reduce((acc, ele) => {acc[ele] = ele; return acc;} ,{}) : currentTab.origins[0]; const subject = { source: `(${currentTab.code})` }; let originContext; // метод getData() хелпера датасет-драйвера в режиме бекенда не пересылает контекст, // из-за чего передача датасета выглядит по-разному в двух режимах. if (env.isBackendMode) { subject.origin = origins; subject.separateDatasets = true; } else { originContext = currentTab.origins.length > 1 ? await Promise.all(Object.keys(origins).map(async(id) => { return { [id]: await this.pullData(id) }; })) : await this.pullData(origins); } this.pullData(subject, null, null, originContext).then(response => { if (response){ this.showSuccess(currentTab, response); }else{ this.showEmpty(currentTab); } }).catch(response => { this.showError(currentTab, response.message ?? response); }); }, onChange(code) { const currentTab = this.tabs[this.selectedTab]; currentTab.code = code; this.manualAutoExec(); }, onOriginChange(origins) { this.tabs[this.selectedTab].origins = origins; this.manualAutoExec(true); } } }; </script> <style scoped> header.v-toolbar { height: 96px; } .split:not(.area-space){ height: calc(100% - 96px); border: solid 1px #eee; } .response { padding: 1em; } .btn-copy { opacity: 0; } .tab:hover .btn-copy { opacity: 1; } </style>