Compare commits
34 Commits
pr@dev@per
...
v5_refacto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65392f1d0c | ||
|
|
777b139fde | ||
|
|
cccd2cbb7a | ||
|
|
f75a4007f6 | ||
|
|
a9c1e06706 | ||
|
|
b66b779e79 | ||
|
|
841ba521f6 | ||
|
|
d2feaa021e | ||
|
|
c231f66e4a | ||
|
|
7fe6ab5f1b | ||
|
|
c037b52bb6 | ||
|
|
37a45cbc6e | ||
|
|
3d90d56373 | ||
|
|
587f49286f | ||
|
|
5e1b32aaf9 | ||
|
|
e1bc8245c9 | ||
|
|
6b6de2b2c5 | ||
|
|
7913979b4b | ||
|
|
16d8c7c9ac | ||
|
|
768210ef86 | ||
|
|
ce9d598683 | ||
|
|
2d75514aa1 | ||
|
|
669831c533 | ||
|
|
eb3f3ba441 | ||
|
|
46cf67e10f | ||
|
|
4e0b20b8e8 | ||
|
|
a6faad4b93 | ||
|
|
cb51ccae61 | ||
|
|
5a9d94aad0 | ||
|
|
69bdc7c0a2 | ||
|
|
2441c08da5 | ||
|
|
f9c244f006 | ||
|
|
9c5ff2b3a8 | ||
|
|
91bfb4d723 |
@@ -1,4 +1,4 @@
|
||||
FROM jumpserver/lina-base:20251030_071727 AS stage-build
|
||||
FROM jumpserver/lina-base:20251204_081759 AS stage-build
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
|
||||
11
package.json
@@ -59,9 +59,11 @@
|
||||
"npm": "^7.8.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"sortablejs": "^1.15.6",
|
||||
"uuid": "8.3.2",
|
||||
"v-sanitize": "^0.0.13",
|
||||
"vue": "2.6.10",
|
||||
"vue": "2.7.16",
|
||||
"vue-codemirror": "4.0.6",
|
||||
"vue-cookie": "^1.1.4",
|
||||
"vue-echarts": "^5.0.0-beta.0",
|
||||
@@ -119,11 +121,11 @@
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "1.2.2",
|
||||
"vue-i18n-extract": "^1.1.1",
|
||||
"vue-template-compiler": "2.6.10",
|
||||
"vue-template-compiler": "2.7.16",
|
||||
"webpack": "^4.28.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
"node": ">=12",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
@@ -140,5 +142,6 @@
|
||||
"src/**/*.{js,vue}": [
|
||||
"eslint --fix"
|
||||
]
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 584 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -107,6 +107,10 @@ export default {
|
||||
this.$emit('add', true)
|
||||
}
|
||||
}).catch(error => {
|
||||
if (error?.response?.data?.code === 'no_valid_assets') {
|
||||
this.$message.error(error?.response?.data?.detail)
|
||||
return
|
||||
}
|
||||
this.iVisible = true
|
||||
this.handleResult(null, error)
|
||||
})
|
||||
|
||||
@@ -1,12 +1,60 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div v-if="hasPrompt" class="chat-action">
|
||||
<Select2
|
||||
v-model="select.value"
|
||||
:disabled="isLoading || isSelectDisabled"
|
||||
v-bind="select"
|
||||
@change="onSelectChange"
|
||||
/>
|
||||
<div class="chat-action">
|
||||
<div class="model-select">
|
||||
<Select2
|
||||
v-model="select.value"
|
||||
:disabled="isLoading || isSelectDisabled || loading || !options.length"
|
||||
v-bind="select"
|
||||
@change="onSelectChange"
|
||||
/>
|
||||
</div>
|
||||
<el-dropdown
|
||||
:hide-on-click="false"
|
||||
trigger="click"
|
||||
>
|
||||
<span class="el-dropdown-link">
|
||||
<i class="fa fa-plug" />
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<div class="menu-section">
|
||||
<div v-if="toolsLoading">
|
||||
<i class="el-icon-loading" /> {{ $t('Loading') }}
|
||||
</div>
|
||||
<div v-else class="menu-body">
|
||||
<div>
|
||||
<div
|
||||
v-for="item in toolOptions"
|
||||
:key="item.value"
|
||||
>
|
||||
<div style="padding: 0 10px">
|
||||
<i class="fa fa-wrench item-icon" />
|
||||
<span class="item-label">{{ item.label }}</span>
|
||||
|
||||
<el-switch
|
||||
:value="selectedToolsSet.has(item.value)"
|
||||
@change="() => toggleTool(item.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in toolServerOptions"
|
||||
:key="item.value"
|
||||
>
|
||||
<div>
|
||||
<i class="fa fa-server item-icon" />
|
||||
<span class="item-label">{{ item.label }}</span>
|
||||
<el-switch
|
||||
:value="selectedToolServersSet.has(item.value)"
|
||||
@change="() => toggleToolServer(item.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="chat-input">
|
||||
<el-input
|
||||
@@ -37,9 +85,37 @@ export default {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hasPrompt: {
|
||||
modelOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedModel: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: false
|
||||
},
|
||||
toolOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
toolServerOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedTools: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedToolServers: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
toolsLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -47,15 +123,10 @@ export default {
|
||||
isIM: false,
|
||||
inputValue: '',
|
||||
select: {
|
||||
url: '/api/v1/settings/chatai-prompts/',
|
||||
value: '',
|
||||
multiple: false,
|
||||
placeholder: this.$t('Role'),
|
||||
ajax: {
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.content }
|
||||
}
|
||||
}
|
||||
placeholder: this.$t('Model'),
|
||||
options: []
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -64,7 +135,32 @@ export default {
|
||||
isLoading: state => state.chat.loading
|
||||
}),
|
||||
isSelectDisabled() {
|
||||
return !!this.select.value
|
||||
return false
|
||||
},
|
||||
options() {
|
||||
return (this.modelOptions || []).map(item => {
|
||||
return { label: item.name || item.id, value: item.id }
|
||||
})
|
||||
},
|
||||
selectedToolsSet() {
|
||||
return new Set(this.selectedTools || [])
|
||||
},
|
||||
selectedToolServersSet() {
|
||||
return new Set(this.selectedToolServers || [])
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelOptions: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.select.options = (val || []).map(item => ({ label: item.name || item.id, value: item.id }))
|
||||
}
|
||||
},
|
||||
selectedModel: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.select.value = val || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -84,7 +180,25 @@ export default {
|
||||
this.inputValue = ''
|
||||
},
|
||||
onSelectChange(value) {
|
||||
this.$emit('select-prompt', value)
|
||||
this.$emit('select-model', value)
|
||||
},
|
||||
toggleTool(id) {
|
||||
const set = new Set(this.selectedTools || [])
|
||||
if (set.has(id)) {
|
||||
set.delete(id)
|
||||
} else {
|
||||
set.add(id)
|
||||
}
|
||||
this.$emit('select-tools', Array.from(set))
|
||||
},
|
||||
toggleToolServer(id) {
|
||||
const set = new Set(this.selectedToolServers || [])
|
||||
if (set.has(id)) {
|
||||
set.delete(id)
|
||||
} else {
|
||||
set.add(id)
|
||||
}
|
||||
this.$emit('select-tool-servers', Array.from(set))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +213,18 @@ export default {
|
||||
.chat-action {
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.model-select {
|
||||
flex: 0 0 48%;
|
||||
max-width: 240px;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
&::v-deep .el-select {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
|
||||
.el-input__inner {
|
||||
height: 28px;
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
<div class="chart-item-container">
|
||||
<div class="avatar">
|
||||
<el-avatar
|
||||
:src="isUserRole ? userUrl : chatUrl"
|
||||
v-if="isUserRole"
|
||||
:src="userUrl"
|
||||
class="header-avatar"
|
||||
/>
|
||||
<el-avatar v-else class="header-avatar" :style="{ backgroundColor: 'transparent' }">
|
||||
<ModelIcon :name="modelIconName" class-name="model-icon" />
|
||||
</el-avatar>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="operational">
|
||||
@@ -76,6 +80,7 @@
|
||||
|
||||
<script>
|
||||
import MessageText from './MessageText.vue'
|
||||
import ModelIcon from '../../models/ModelIcon.vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { copy } from '@/utils/common/index'
|
||||
import { useChat } from '../../useChat.js'
|
||||
@@ -85,7 +90,8 @@ const { setLoading, removeLoadingMessageInChat } = useChat()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MessageText
|
||||
MessageText,
|
||||
ModelIcon
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
@@ -93,6 +99,10 @@ export default {
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
selectedModel: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isTerminal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -129,10 +139,8 @@ export default {
|
||||
? this.$i18n.t('ServerBusyRetry')
|
||||
: ''
|
||||
},
|
||||
chatUrl() {
|
||||
return this.publicSettings.CHAT_AI_TYPE === 'gpt'
|
||||
? require('@/assets/img/chat.png')
|
||||
: require('@/assets/img/deepSeek.png')
|
||||
modelIconName() {
|
||||
return (this.item?.message?.model || this.selectedModel || this.publicSettings.CHAT_AI_TYPE || '').toString()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -171,11 +179,18 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
|
||||
&::v-deep img {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.model-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
text() {
|
||||
const value = this.message?.content || ''
|
||||
if (value && this.markdown) {
|
||||
return this.markdown?.render(value)
|
||||
return this.renderContentWithDetails(value)
|
||||
}
|
||||
return this.$xss.process(value)
|
||||
}
|
||||
@@ -120,6 +120,77 @@ export default {
|
||||
btn.addEventListener('click', callback)
|
||||
})
|
||||
},
|
||||
renderContentWithDetails(value) {
|
||||
// Kael responses may wrap reasoning/thinking in <details type="reasoning">; render them with a custom block.
|
||||
const detailRegex = /<details[^>]*>[\s\S]*?<\/details>/gi
|
||||
let result = ''
|
||||
let lastIndex = 0
|
||||
let match
|
||||
let hasDetails = false
|
||||
|
||||
while ((match = detailRegex.exec(value))) {
|
||||
hasDetails = true
|
||||
const preceding = value.slice(lastIndex, match.index)
|
||||
if (preceding.trim()) {
|
||||
result += this.markdown.render(preceding)
|
||||
}
|
||||
result += this.renderDetailBlock(match[0])
|
||||
lastIndex = match.index + match[0].length
|
||||
}
|
||||
|
||||
if (!hasDetails) {
|
||||
return this.markdown.render(value)
|
||||
}
|
||||
|
||||
const remaining = value.slice(lastIndex)
|
||||
if (remaining.trim()) {
|
||||
result += this.markdown.render(remaining)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
renderDetailBlock(detailStr) {
|
||||
const attributes = this.extractAttributes(detailStr)
|
||||
const inner = detailStr.replace(/^<details[^>]*>/i, '').replace(/<\/details>$/i, '')
|
||||
const summaryMatch = inner.match(/<summary>([\s\S]*?)<\/summary>/i)
|
||||
const summary = summaryMatch ? this.decodeHtml(summaryMatch[1]) : ''
|
||||
const body = summaryMatch ? inner.replace(summaryMatch[0], '') : inner
|
||||
const bodyHtml = body.trim() ? this.markdown.render(this.decodeHtml(body.trim())) : ''
|
||||
|
||||
const baseClass = 'kael-detail'
|
||||
if (attributes.type === 'reasoning') {
|
||||
const statusClass = attributes.done === 'true' ? 'is-done' : 'is-pending'
|
||||
const title = summary || this.$t('DeeplyThoughtAbout')
|
||||
return `<div class="${baseClass} ${baseClass}--reasoning ${statusClass}">
|
||||
<div class="${baseClass}__header">
|
||||
<span class="${baseClass}__status-dot"></span>
|
||||
<span class="${baseClass}__title">${title}</span>
|
||||
</div>
|
||||
${bodyHtml ? `<div class="${baseClass}__body">${bodyHtml}</div>` : ''}
|
||||
</div>`
|
||||
}
|
||||
|
||||
return `<div class="${baseClass}">
|
||||
${summary ? `<div class="${baseClass}__header">${summary}</div>` : ''}
|
||||
${bodyHtml ? `<div class="${baseClass}__body">${bodyHtml}</div>` : ''}
|
||||
</div>`
|
||||
},
|
||||
extractAttributes(detailStr) {
|
||||
const attrs = {}
|
||||
const attrMatch = detailStr.match(/^<details([^>]*)>/i)
|
||||
const attrStr = (attrMatch && attrMatch[1]) || ''
|
||||
attrStr.replace(/(\w+)="(.*?)"/g, (all, key, val) => {
|
||||
attrs[key] = val
|
||||
return all
|
||||
})
|
||||
return attrs
|
||||
},
|
||||
decodeHtml(str) {
|
||||
if (!str) return ''
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.innerHTML = str
|
||||
return textArea.value
|
||||
},
|
||||
removeBtnClickEvent(selector) {
|
||||
const buttons = this.$refs.textRef.querySelectorAll(selector)
|
||||
buttons.forEach((btn) => {
|
||||
@@ -258,4 +329,64 @@ export default {
|
||||
.loading-box span:nth-child(3) {
|
||||
animation-delay: 0.49s;
|
||||
}
|
||||
|
||||
.kael-detail {
|
||||
margin: 8px 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e5e5;
|
||||
background: #f7f8fa;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 6px;
|
||||
padding-left: 8px;
|
||||
border-left: 2px solid #e5e5e5;
|
||||
}
|
||||
|
||||
&--reasoning.is-pending {
|
||||
border-color: #f59e0b40;
|
||||
background: #fff8e6;
|
||||
|
||||
.kael-detail__status-dot {
|
||||
background: #f59e0b;
|
||||
animation: kael-pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
&--reasoning.is-done {
|
||||
border-color: #dbeafe;
|
||||
background: #f4f6ff;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kael-pulse {
|
||||
0% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import Chat from './components/ChitChat/index.vue'
|
||||
import { getInputFocus } from './useChat.js'
|
||||
import { ws } from '@/utils/request'
|
||||
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
||||
import { ObjectLocalStorage } from '@/utils/common'
|
||||
import { mapGetters } from 'vuex'
|
||||
@@ -82,7 +81,8 @@ export default {
|
||||
height: '400px',
|
||||
expanded: false,
|
||||
clientOffset: {},
|
||||
currentTerminalContent: {}
|
||||
currentTerminalContent: {},
|
||||
initialized: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -123,11 +123,18 @@ export default {
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
},
|
||||
initAssistant() {
|
||||
if (this.initialized) return
|
||||
this.initialized = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.component?.init()
|
||||
})
|
||||
},
|
||||
handlePostMessage() {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data === 'show-chat-panel') {
|
||||
this.$refs.drawer.show = true
|
||||
this.initWebSocket()
|
||||
this.initAssistant()
|
||||
return
|
||||
}
|
||||
const msg = event.data
|
||||
@@ -152,11 +159,6 @@ export default {
|
||||
}
|
||||
this.$refs.drawer.handleHeaderMoveUp(event)
|
||||
},
|
||||
initWebSocket() {
|
||||
if (!ws) {
|
||||
this.$refs.component?.init()
|
||||
}
|
||||
},
|
||||
onClose() {
|
||||
this.$refs.drawer.show = false
|
||||
},
|
||||
@@ -170,7 +172,6 @@ export default {
|
||||
},
|
||||
save_pannel_settings() {
|
||||
aiPannelLocalStorage.set('expanded', this.expanded)
|
||||
console.log('AI panel settings saved:', this.expanded)
|
||||
},
|
||||
updateExpandedState(expanded) {
|
||||
this.expanded = expanded
|
||||
@@ -184,8 +185,8 @@ export default {
|
||||
})
|
||||
},
|
||||
onToggle(status) {
|
||||
this.initWebSocket()
|
||||
if (status) {
|
||||
this.initAssistant()
|
||||
getInputFocus()
|
||||
}
|
||||
}
|
||||
|
||||
33
src/components/Apps/ChatAi/models/ChatGPT.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg
|
||||
:stroke-width="strokeWidth"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:class="className"
|
||||
>
|
||||
<path
|
||||
fill="#6b7280"
|
||||
style="fill: #6b7280 !important"
|
||||
d="M11.2475 18.25C10.6975 18.25 10.175 18.1455 9.67999 17.9365C9.18499 17.7275 8.74499 17.436 8.35999 17.062C7.94199 17.205 7.50749 17.2765 7.05649 17.2765C6.31949 17.2765 5.63749 17.095 5.01049 16.732C4.38349 16.369 3.87749 15.874 3.49249 15.247C3.11849 14.62 2.93149 13.9215 2.93149 13.1515C2.93149 12.8325 2.97549 12.486 3.06349 12.112C2.62349 11.705 2.28249 11.2375 2.04049 10.7095C1.79849 10.1705 1.67749 9.6095 1.67749 9.0265C1.67749 8.4325 1.80399 7.8605 2.05699 7.3105C2.30999 6.7605 2.66199 6.2875 3.11299 5.8915C3.57499 5.4845 4.10849 5.204 4.71349 5.05C4.83449 4.423 5.08749 3.862 5.47249 3.367C5.86849 2.861 6.35249 2.465 6.92449 2.179C7.49649 1.893 8.10699 1.75 8.75599 1.75C9.30599 1.75 9.82849 1.8545 10.3235 2.0635C10.8185 2.2725 11.2585 2.564 11.6435 2.938C12.0615 2.795 12.496 2.7235 12.947 2.7235C13.684 2.7235 14.366 2.905 14.993 3.268C15.62 3.631 16.1205 4.126 16.4945 4.753C16.8795 5.38 17.072 6.0785 17.072 6.8485C17.072 7.1675 17.028 7.514 16.94 7.888C17.38 8.295 17.721 8.768 17.963 9.307C18.205 9.835 18.326 10.3905 18.326 10.9735C18.326 11.5675 18.1995 12.1395 17.9465 12.6895C17.6935 13.2395 17.336 13.718 16.874 14.125C16.423 14.521 15.895 14.796 15.29 14.95C15.169 15.577 14.9105 16.138 14.5145 16.633C14.1295 17.139 13.651 17.535 13.079 17.821C12.507 18.107 11.8965 18.25 11.2475 18.25ZM7.17199 16.1875C7.72199 16.1875 8.20049 16.072 8.60749 15.841L11.7095 14.059C11.8195 13.982 11.8745 13.8775 11.8745 13.7455V12.3265L7.88149 14.62C7.63949 14.763 7.39749 14.763 7.15549 14.62L4.03699 12.8215C4.03699 12.8545 4.03149 12.893 4.02049 12.937C4.02049 12.981 4.02049 13.047 4.02049 13.135C4.02049 13.696 4.15249 14.213 4.41649 14.686C4.69149 15.148 5.07099 15.511 5.55499 15.775C6.03899 16.05 6.57799 16.1875 7.17199 16.1875ZM7.33699 13.498C7.40299 13.531 7.46349 13.5475 7.51849 13.5475C7.57349 13.5475 7.62849 13.531 7.68349 13.498L8.92099 12.7885L4.94449 10.4785C4.70249 10.3355 4.58149 10.121 4.58149 9.835V6.2545C4.03149 6.4965 3.59149 6.8705 3.26149 7.3765C2.93149 7.8715 2.76649 8.4215 2.76649 9.0265C2.76649 9.5655 2.90399 10.0825 3.17899 10.5775C3.45399 11.0725 3.81149 11.4465 4.25149 11.6995L7.33699 13.498ZM11.2475 17.161C11.8305 17.161 12.3585 17.029 12.8315 16.765C13.3045 16.501 13.6785 16.138 13.9535 15.676C14.2285 15.214 14.366 14.697 14.366 14.125V10.561C14.366 10.429 14.311 10.33 14.201 10.264L12.947 9.538V14.1415C12.947 14.4275 12.826 14.642 12.584 14.785L9.46549 16.5835C10.0045 16.9685 10.5985 17.161 11.2475 17.161ZM11.8745 11.122V8.878L10.01 7.822L8.12899 8.878V11.122L10.01 12.178L11.8745 11.122ZM7.05649 5.8585C7.05649 5.5725 7.17749 5.358 7.41949 5.215L10.538 3.4165C9.99899 3.0315 9.40499 2.839 8.75599 2.839C8.17299 2.839 7.64499 2.971 7.17199 3.235C6.69899 3.499 6.32499 3.862 6.04999 4.324C5.78599 4.786 5.65399 5.303 5.65399 5.875V9.4225C5.65399 9.5545 5.70899 9.659 5.81899 9.736L7.05649 10.462V5.8585ZM15.4385 13.7455C15.9885 13.5035 16.423 13.1295 16.742 12.6235C17.072 12.1175 17.237 11.5675 17.237 10.9735C17.237 10.4345 17.0995 9.9175 16.8245 9.4225C16.5495 8.9275 16.192 8.5535 15.752 8.3005L12.6665 6.5185C12.6005 6.4745 12.54 6.458 12.485 6.469C12.43 6.469 12.375 6.4855 12.32 6.5185L11.0825 7.2115L15.0755 9.538C15.1965 9.604 15.2845 9.692 15.3395 9.802C15.4055 9.901 15.4385 10.022 15.4385 10.165V13.7455ZM12.122 5.3635C12.364 5.2095 12.606 5.2095 12.848 5.3635L15.983 7.195C15.983 7.118 15.983 7.019 15.983 6.898C15.983 6.37 15.851 5.8695 15.587 5.3965C15.334 4.9125 14.9655 4.5275 14.4815 4.2415C14.0085 3.9555 13.4585 3.8125 12.8315 3.8125C12.2815 3.8125 11.803 3.928 11.396 4.159L8.29399 5.941C8.18399 6.018 8.12899 6.1225 8.12899 6.2545V7.6735L12.122 5.3635Z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChatGPTIcon',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-8'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
45
src/components/Apps/ChatAi/models/Claude.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<svg
|
||||
:stroke-width="strokeWidth"
|
||||
viewBox="0 0 24 16"
|
||||
overflow="visible"
|
||||
width="20"
|
||||
height="20"
|
||||
:class="className"
|
||||
>
|
||||
<g style="transform: translateX(13px) rotateZ(0deg); transform-origin: 4.775px 7.73501px;">
|
||||
<path
|
||||
shape-rendering="geometricPrecision"
|
||||
fill-opacity="1"
|
||||
fill="#8c653f"
|
||||
style="fill: #8c653f !important"
|
||||
d=" M0,0 C0,0 6.1677093505859375,15.470022201538086 6.1677093505859375,15.470022201538086 C6.1677093505859375,15.470022201538086 9.550004005432129,15.470022201538086 9.550004005432129,15.470022201538086 C9.550004005432129,15.470022201538086 3.382294178009033,0 3.382294178009033,0 C3.382294178009033,0 0,0 0,0 C0,0 0,0 0,0z"
|
||||
/>
|
||||
</g>
|
||||
<g opacity="1" style="transform: none; transform-origin: 7.935px 7.73501px;">
|
||||
<path
|
||||
shape-rendering="geometricPrecision"
|
||||
fill-opacity="1"
|
||||
fill="#8c653f"
|
||||
style="fill: #8c653f !important"
|
||||
d=" M5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 7.93500280380249,3.911694288253784 7.93500280380249,3.911694288253784 C7.93500280380249,3.911694288253784 10.045400619506836,9.348296165466309 10.045400619506836,9.348296165466309 C10.045400619506836,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309z M6.166755199432373,0 C6.166755199432373,0 0,15.470022201538086 0,15.470022201538086 C0,15.470022201538086 3.4480772018432617,15.470022201538086 3.4480772018432617,15.470022201538086 C3.4480772018432617,15.470022201538086 4.709278583526611,12.22130012512207 4.709278583526611,12.22130012512207 C4.709278583526611,12.22130012512207 11.16093635559082,12.22130012512207 11.16093635559082,12.22130012512207 C11.16093635559082,12.22130012512207 12.421928405761719,15.470022201538086 12.421928405761719,15.470022201538086 C12.421928405761719,15.470022201538086 15.87000560760498,15.470022201538086 15.87000560760498,15.470022201538086 C15.87000560760498,15.470022201538086 9.703250885009766,0 9.703250885009766,0 C9.703250885009766,0 6.166755199432373,0 6.166755199432373,0 C6.166755199432373,0 6.166755199432373,0 6.166755199432373,0z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClaudeIcon',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-4'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
34
src/components/Apps/ChatAi/models/DeepSeek.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<svg
|
||||
id="图层_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-name="图层 1"
|
||||
viewBox="0 0 71.69 52.76"
|
||||
:class="className"
|
||||
:stroke-width="strokeWidth"
|
||||
>
|
||||
<path
|
||||
id="path"
|
||||
fill="#4d6bfe"
|
||||
style="fill: #4d6bfe !important"
|
||||
d="M523.77,276.34c-.76-.38-1.08.33-1.53.69a4,4,0,0,0-.41.41,5.07,5.07,0,0,1-4.1,1.87,8,8,0,0,0-6.46,2.53,5.82,5.82,0,0,0-3.72-4.62,6.39,6.39,0,0,1-2.85-1.94,7.76,7.76,0,0,1-.92-2.31c-.16-.48-.32-1-.87-1.05s-.83.41-1.07.82a11,11,0,0,0-1.26,5.5,11.9,11.9,0,0,0,5.49,10.14.75.75,0,0,1,.39,1c-.25.84-.54,1.65-.79,2.49-.17.53-.41.65-1,.42a16.63,16.63,0,0,1-5.18-3.52c-2.56-2.48-4.88-5.21-7.76-7.35-.68-.5-1.36-1-2.06-1.41-2.94-2.86.39-5.2,1.16-5.48s.28-1.29-2.33-1.28-5,.88-8,2a8.23,8.23,0,0,1-1.39.41,28.67,28.67,0,0,0-8.61-.3,18.57,18.57,0,0,0-13.44,7.83c-4,5.47-4.91,11.67-3.76,18.15a27.68,27.68,0,0,0,10,16.88,26.8,26.8,0,0,0,19.23,6.39c4.43-.25,9.36-.84,14.92-5.55a13.84,13.84,0,0,0,5.32,1.18,17.24,17.24,0,0,0,5.09-.38c2.2-.46,2.05-2.5,1.25-2.87-6.43-3-5-1.78-6.3-2.77,3.27-3.87,8.2-7.89,10.13-20.92a12.44,12.44,0,0,0,0-2.52c0-.51.1-.71.68-.76a12.55,12.55,0,0,0,4.62-1.42c4.17-2.28,5.85-6,6.25-10.51A1.57,1.57,0,0,0,523.77,276.34Zm-36.34,40.37c-6.24-4.9-9.27-6.52-10.52-6.45s-1,1.41-.7,2.28a8.49,8.49,0,0,0,1.11,2.21,1.14,1.14,0,0,1-.34,1.8c-2,1.24-5.5-.42-5.66-.5a26.08,26.08,0,0,1-9.87-9.88,30.15,30.15,0,0,1-3.87-13.39c-.06-1.15.28-1.56,1.42-1.77a14.31,14.31,0,0,1,4.57-.11,28.56,28.56,0,0,1,16.33,8.29,54.06,54.06,0,0,1,6.58,8.63,41.46,41.46,0,0,0,7.41,8.71,24.36,24.36,0,0,0,2.66,2C494.16,318.82,490.16,318.87,487.43,316.71Zm3-19.23a.92.92,0,0,1,.92-.92.83.83,0,0,1,.32.06.8.8,0,0,1,.34.22.9.9,0,0,1,.25.64.92.92,0,0,1-1.83,0Zm9.29,4.76a5.27,5.27,0,0,1-1.77.48,3.75,3.75,0,0,1-2.38-.76,3.57,3.57,0,0,1-1.65-2.26,5.16,5.16,0,0,1,0-1.76,2,2,0,0,0-.71-2.17,3.1,3.1,0,0,0-2.06-.59,1.63,1.63,0,0,1-.76-.24.75.75,0,0,1-.34-1.07,3.47,3.47,0,0,1,.57-.62,3.9,3.9,0,0,1,3.43,0,10,10,0,0,1,3,2.34,18.62,18.62,0,0,1,2,2.73,10.9,10.9,0,0,1,1.33,2.53C500.65,301.47,500.4,302,499.71,302.24Z"
|
||||
transform="translate(-452.83 -271.91)"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DeepSeekIcon',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-4'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
33
src/components/Apps/ChatAi/models/Gemini.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 512 512"
|
||||
:class="className"
|
||||
:stroke-width="strokeWidth"
|
||||
>
|
||||
<path
|
||||
fill="#4285f4"
|
||||
style="fill: #4285f4 !important"
|
||||
d="M473.16,221.48l-2.26-9.59H262.46v88.22H387c-12.93,61.4-72.93,93.72-121.94,93.72-35.66,0-73.25-15-98.13-39.11a140.08,140.08,0,0,1-41.8-98.88c0-37.16,16.7-74.33,41-98.78s61-38.13,97.49-38.13c41.79,0,71.74,22.19,82.94,32.31l62.69-62.36C390.86,72.72,340.34,32,261.6,32h0c-60.75,0-119,23.27-161.58,65.71C58,139.5,36.25,199.93,36.25,256S56.83,369.48,97.55,411.6C141.06,456.52,202.68,480,266.13,480c57.73,0,112.45-22.62,151.45-63.66,38.34-40.4,58.17-96.3,58.17-154.9C475.75,236.77,473.27,222.12,473.16,221.48Z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GeminiIcon',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-4'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
33
src/components/Apps/ChatAi/models/Grok.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
fill="currentColor"
|
||||
:class="className"
|
||||
:stroke-width="strokeWidth"
|
||||
>
|
||||
<path
|
||||
fill="#000000"
|
||||
style="fill: #000000 !important"
|
||||
d="m3.005 8.858 8.783 12.544h3.904L6.908 8.858zM6.905 15.825 3 21.402h3.907l1.951-2.788zM16.585 2l-6.75 9.64 1.953 2.79L20.492 2zM17.292 7.965v13.437h3.2V3.395z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GrokIcon',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-4'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
50
src/components/Apps/ChatAi/models/ModelIcon.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<component
|
||||
:is="resolvedIcon"
|
||||
v-if="resolvedIcon"
|
||||
:class-name="className"
|
||||
:stroke-width="strokeWidth"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChatGPTIcon from './ChatGPT.vue'
|
||||
import DeepSeekIcon from './DeepSeek.vue'
|
||||
import GrokIcon from './Grok.vue'
|
||||
import ClaudeIcon from './Claude.vue'
|
||||
import GeminiIcon from './Gemini.vue'
|
||||
|
||||
export default {
|
||||
name: 'ModelIcon',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'size-5'
|
||||
},
|
||||
strokeWidth: {
|
||||
type: [String, Number],
|
||||
default: '1.5'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
normalizedName() {
|
||||
return (this.name || '').toLowerCase()
|
||||
},
|
||||
resolvedIcon() {
|
||||
const name = this.normalizedName
|
||||
if (!name) return null
|
||||
if (name.includes('gpt')) return ChatGPTIcon
|
||||
if (name.includes('deep-seek')) return DeepSeekIcon
|
||||
if (name.includes('deepseek')) return DeepSeekIcon
|
||||
if (name.includes('grok')) return GrokIcon
|
||||
if (name.includes('claude')) return ClaudeIcon
|
||||
if (name.includes('gemini')) return GeminiIcon
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -11,6 +11,7 @@
|
||||
import AssetTreeTable from '@/components/Apps/AssetTreeTable'
|
||||
import { AccountInfoFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
|
||||
import { connectivityMeta } from '@/components/Apps/AccountListTable/const'
|
||||
import { setUrlParam } from '@/utils/common/index'
|
||||
|
||||
export default {
|
||||
name: 'GrantedAssets',
|
||||
@@ -34,7 +35,7 @@ export default {
|
||||
}
|
||||
const initialUrl = vm.tableConfig.initialUrl
|
||||
const nodeId = node.meta.data.id
|
||||
const url = initialUrl.replace('/assets/', `/nodes/${nodeId}/assets/`)
|
||||
const url = setUrlParam(initialUrl, 'node_id', nodeId)
|
||||
vm.tableConfig.url = url
|
||||
}
|
||||
},
|
||||
@@ -70,7 +71,7 @@ export default {
|
||||
showMenu: false,
|
||||
showRefresh: true,
|
||||
showAssets: false,
|
||||
showSearch: false,
|
||||
showSearch: true,
|
||||
url: this.tableUrl,
|
||||
// ?assets=0不显示资产. =1显示资产
|
||||
treeUrl: this.treeUrl,
|
||||
@@ -78,6 +79,9 @@ export default {
|
||||
callback: {
|
||||
onSelected: (event, node) => vm.onSelected(node, vm),
|
||||
refresh: vm.refreshObjectAssetPermission
|
||||
},
|
||||
async: {
|
||||
enable: false
|
||||
}
|
||||
},
|
||||
tableConfig: {
|
||||
|
||||
@@ -37,8 +37,12 @@ export default {
|
||||
|
||||
},
|
||||
headerActions: {
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasExport: true,
|
||||
hasImport: true,
|
||||
importOptions: {
|
||||
encryptFields: [''], // 这里不加密 password,''只是为了保证数组有值
|
||||
canImportUpdate: false
|
||||
},
|
||||
hasCreate: true,
|
||||
hasSearch: true,
|
||||
hasRefresh: true,
|
||||
|
||||
@@ -73,28 +73,28 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.help-dialog.dialog .el-dialog__footer {
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .help-dialog.dialog .el-dialog__footer {
|
||||
border-top: none;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
&::v-deep th, td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
::v-deep .help-table th,
|
||||
::v-deep .help-table td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&::v-deep .item-td, .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
::v-deep .help-table .item-td,
|
||||
::v-deep .help-table .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -223,8 +223,21 @@ export default {
|
||||
const mapped = {}
|
||||
Object.entries(errors || {}).forEach(([k, v]) => {
|
||||
let msg = v
|
||||
if (Array.isArray(v)) msg = v.join('; ')
|
||||
else if (typeof v === 'object' && v !== null) msg = JSON.stringify(v)
|
||||
console.log(k, v)
|
||||
// v是数组并且数组都是字符串,则拼接为字符串
|
||||
if (Array.isArray(v) && v.every(item => typeof item === 'string')) msg = v.join('; ')
|
||||
// 处理 [{"port":["请确保该值小于或者等于 65535。"]},{},{}] 这种情况
|
||||
else if (Array.isArray(v) && v.every(item => _.isPlainObject(item))) {
|
||||
const subMsg = []
|
||||
v.forEach((subItem) => {
|
||||
Object.values(subItem).forEach((subMsgArr) => {
|
||||
if (Array.isArray(subMsgArr)) {
|
||||
subMsg.push(...subMsgArr)
|
||||
}
|
||||
})
|
||||
})
|
||||
msg = subMsg.join(' ')
|
||||
} else if (typeof v === 'object' && v !== null) msg = JSON.stringify(v)
|
||||
mapped[k] = String(msg || '')
|
||||
})
|
||||
this.serverErrors = mapped
|
||||
|
||||
@@ -149,7 +149,7 @@ export default {
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '25%'
|
||||
default: '18.2%'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
:import-option="importOption"
|
||||
:json-data="jsonData"
|
||||
:url="url"
|
||||
v-bind="$attrs"
|
||||
@cancel="cancelUpload"
|
||||
@finish="closeDialog"
|
||||
/>
|
||||
@@ -247,46 +248,46 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import "~@/styles/variables";
|
||||
@import "~@/styles/variables";
|
||||
|
||||
.error-msg {
|
||||
color: $--color-danger;
|
||||
.error-msg {
|
||||
color: $--color-danger;
|
||||
}
|
||||
|
||||
.error-msg.error-results {
|
||||
background-color: #f3f3f4;
|
||||
max-height: 200px;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.file-uploader ::v-deep .el-upload {
|
||||
width: 100%;
|
||||
//padding-right: 150px;
|
||||
}
|
||||
|
||||
.file-uploader ::v-deep .el-upload-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.importTableZone {
|
||||
padding: 0 20px;
|
||||
|
||||
.importTable {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.error-msg.error-results {
|
||||
background-color: #f3f3f4;
|
||||
max-height: 200px;
|
||||
overflow: auto
|
||||
.tableFilter {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-uploader ::v-deep .el-upload {
|
||||
width: 100%;
|
||||
//padding-right: 150px;
|
||||
}
|
||||
.importTable ::v-deep .el-dialog__body {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-uploader ::v-deep .el-upload-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.importTableZone {
|
||||
padding: 0 20px;
|
||||
|
||||
.importTable {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tableFilter {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.importTable ::v-deep .el-dialog__body {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.export-item {
|
||||
margin-left: 80px;
|
||||
}
|
||||
.export-item {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.export-item:first-child {
|
||||
margin-left: 0;
|
||||
|
||||
@@ -97,6 +97,10 @@ export default {
|
||||
origin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
encryptFields: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -273,11 +277,15 @@ export default {
|
||||
}
|
||||
return columns
|
||||
},
|
||||
getEncryptFields() {
|
||||
const fromProp = Array.isArray(this.encryptFields) && this.encryptFields.length ? this.encryptFields : null
|
||||
return fromProp || ['password', 'secret', 'private_key']
|
||||
},
|
||||
generateTableData(tableTitles, tableData) {
|
||||
const totalData = []
|
||||
tableData.forEach(item => {
|
||||
this.$set(item, '@status', 'pending')
|
||||
const encryptFields = ['password', 'secret', 'private_key']
|
||||
const encryptFields = this.getEncryptFields()
|
||||
for (const field of encryptFields) {
|
||||
if (item[field]) {
|
||||
item[field] = encryptPassword(item[field])
|
||||
|
||||
@@ -421,12 +421,8 @@ export default {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.data-z-tree {
|
||||
::v-deep {
|
||||
.icon {
|
||||
width: 10px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
.data-z-tree ::v-deep .icon {
|
||||
width: 10px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="treebox">
|
||||
<div v-if="treeSetting.showSearch">
|
||||
<div v-if="treeSetting.showSearch" @click="focusTreeSearchInput">
|
||||
<el-input
|
||||
v-show="showTreeSearch"
|
||||
ref="treeSearchInput"
|
||||
v-model="treeSearchValue"
|
||||
:placeholder="$tc('Search')"
|
||||
class="fixed-tree-search"
|
||||
prefix-icon="fa fa-search"
|
||||
:placeholder="treeSearchInputPlaceholder"
|
||||
size="mini"
|
||||
@input="treeSearchHandle"
|
||||
>
|
||||
<template #prepend>
|
||||
|
||||
<template v-if="!isSearchTypeDropdownEnabled">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="currentTreeSearchTypeTooltip"
|
||||
:open-delay="300"
|
||||
>
|
||||
<span style="cursor: pointer;" @click.stop="focusTreeSearchInput">
|
||||
<i class="fa fa-search" />
|
||||
<span class="search-label">{{ treeSearchTypeLabel }}</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-dropdown trigger="hover" @command="onSearchTypeChange">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
placement="top"
|
||||
:content="currentTreeSearchTypeTooltip"
|
||||
:open-delay="1000"
|
||||
>
|
||||
<span @click.stop="focusTreeSearchInput">
|
||||
<i class="fa fa-search" />
|
||||
<span class="search-label">{{ treeSearchTypeLabel }}</span>
|
||||
<i class="el-icon-arrow-down" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(item, type) in treeSearchTypeOptions"
|
||||
:key="type"
|
||||
:command="type"
|
||||
:class="{ 'is-active': treeSearchType === type }"
|
||||
>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
placement="right"
|
||||
:content="item.tooltip"
|
||||
:open-delay="300"
|
||||
>
|
||||
<span>{{ item.label }}</span>
|
||||
</el-tooltip>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<span slot="suffix">
|
||||
<i
|
||||
class="el-icon-close"
|
||||
@@ -47,6 +98,7 @@ import '@ztree/ztree_v3/js/jquery.ztree.exhide.min.js'
|
||||
import '@/styles/ztree.css'
|
||||
import '@/styles/ztree_icon.scss'
|
||||
import axiosRetry from 'axios-retry'
|
||||
import { setUrlParam } from '@/utils/common'
|
||||
|
||||
const defaultObject = {
|
||||
type: Object,
|
||||
@@ -66,18 +118,52 @@ export default {
|
||||
rMenu: '',
|
||||
init: false,
|
||||
loading: false,
|
||||
showTreeSearch: false,
|
||||
treeSearchValue: ''
|
||||
showTreeSearch: true,
|
||||
treeSearchValue: '',
|
||||
treeSearchType: 'asset',
|
||||
treeSearchTypeOptions: {},
|
||||
treeSearchTypeSupportOptions: {
|
||||
node: {
|
||||
label: this.$t('Node'),
|
||||
placeholder: this.$t('Search node'),
|
||||
tooltip: this.$t('Search by node name'),
|
||||
search_key: 'search_node'
|
||||
},
|
||||
asset: {
|
||||
label: this.$t('Asset'),
|
||||
placeholder: this.$t('Search asset'),
|
||||
tooltip: this.$t('Search by asset name or address'),
|
||||
search_key: 'search_asset'
|
||||
}
|
||||
},
|
||||
treeType: '' // asset | node
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
treeSetting() {
|
||||
return this.setting
|
||||
},
|
||||
isSearchTypeDropdownEnabled() {
|
||||
return Object.keys(this.treeSearchTypeOptions).length > 1
|
||||
},
|
||||
currentTreeSearchType() {
|
||||
return this.treeSearchTypeOptions[this.treeSearchType]
|
||||
},
|
||||
currentTreeSearchTypeTooltip() {
|
||||
return this.currentTreeSearchType?.tooltip || ''
|
||||
},
|
||||
treeSearchTypeLabel() {
|
||||
return this.currentTreeSearchType?.label || ''
|
||||
},
|
||||
treeSearchInputPlaceholder() {
|
||||
return this.currentTreeSearchType?.placeholder || this.$t('Search')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.refresh = this.refresh
|
||||
window.onSearch = this.onSearch
|
||||
this.initTreeType()
|
||||
this.initTreeSearchTypeOptions()
|
||||
this.initTree().then(() => {
|
||||
this.$nextTick(() => {
|
||||
this.updateTreeHeight()
|
||||
@@ -90,6 +176,36 @@ export default {
|
||||
window.removeEventListener('resize', this.updateTreeHeight)
|
||||
},
|
||||
methods: {
|
||||
initTreeType() {
|
||||
let treeType = this.treeSetting.treeType
|
||||
if (!treeType) {
|
||||
treeType = this.treeSetting.async?.enable ? 'asset' : 'node'
|
||||
}
|
||||
this.treeType = treeType
|
||||
},
|
||||
initTreeSearchTypeOptions() {
|
||||
if (this.treeType === 'asset') {
|
||||
// 资产树支持异步搜索节点和资产
|
||||
this.treeSearchTypeOptions = this.treeSearchTypeSupportOptions
|
||||
// 默认搜索资产
|
||||
this.treeSearchType = 'asset'
|
||||
} else {
|
||||
// 节点树只支持搜索节点
|
||||
this.treeSearchTypeOptions = Object.fromEntries(
|
||||
Object.entries(this.treeSearchTypeSupportOptions)
|
||||
.filter(([key]) => key === 'node')
|
||||
)
|
||||
// 默认搜索节点
|
||||
this.treeSearchType = 'node'
|
||||
}
|
||||
},
|
||||
onSearchTypeChange(type) {
|
||||
this.treeSearchType = type
|
||||
this.focusTreeSearchInput()
|
||||
},
|
||||
focusTreeSearchInput() {
|
||||
this.$refs.treeSearchInput.focus()
|
||||
},
|
||||
onMenuClick(menu) {
|
||||
if (menu.disabled) {
|
||||
return
|
||||
@@ -116,19 +232,14 @@ export default {
|
||||
const zTreeRect = tree.getBoundingClientRect()
|
||||
tree.style.height = `calc(100vh - ${zTreeRect.top}px - 30px - 25px)`
|
||||
}, 100),
|
||||
async initTree(refresh = false) {
|
||||
async initTree(refresh = false, iTreeUrl = '') {
|
||||
const vm = this
|
||||
let treeUrl
|
||||
this.loading = true
|
||||
if (refresh && this.treeSetting.treeUrl.indexOf('/perms/') !== -1 &&
|
||||
this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1
|
||||
) {
|
||||
treeUrl = (this.treeSetting.treeUrl.indexOf('?') === -1)
|
||||
? `${this.treeSetting.treeUrl}?rebuild_tree=1`
|
||||
: `${this.treeSetting.treeUrl}&rebuild_tree=1`
|
||||
} else {
|
||||
let treeUrl = iTreeUrl
|
||||
if (!treeUrl) {
|
||||
treeUrl = this.treeSetting.treeUrl
|
||||
}
|
||||
treeUrl = setUrlParam(treeUrl, 'tree_type', this.treeType)
|
||||
|
||||
if (refresh) {
|
||||
$.fn.zTree.destroy(this.iZTreeID)
|
||||
@@ -221,25 +332,31 @@ export default {
|
||||
searchInput.oninput = e => this.treeSearchHandle((e.target.value || ''))
|
||||
},
|
||||
treeSearchHandle: _.debounce(function(value) {
|
||||
if (this.treeSetting.async.enable) {
|
||||
this.filterAssetsServer(value)
|
||||
if (this.treeSetting.async?.enable) {
|
||||
this.searchFromServer(value)
|
||||
} else {
|
||||
this.filterTree(value)
|
||||
this.searchFromLocal(value)
|
||||
}
|
||||
}, 600),
|
||||
getCheckedNodes: function() {
|
||||
return this.zTree.getCheckedNodes(true)
|
||||
},
|
||||
|
||||
recurseParent(node) {
|
||||
const parentNode = node.getParentNode()
|
||||
if (parentNode && parentNode.pId) {
|
||||
return [parentNode, ...this.recurseParent(parentNode)]
|
||||
} else if (parentNode) {
|
||||
return [parentNode]
|
||||
} else {
|
||||
if (!parentNode) {
|
||||
return []
|
||||
}
|
||||
const allParents = []
|
||||
if (parentNode) {
|
||||
allParents.push(parentNode)
|
||||
if (parentNode.pId) {
|
||||
allParents.push(...this.recurseParent(parentNode))
|
||||
}
|
||||
}
|
||||
return allParents
|
||||
},
|
||||
|
||||
recurseChildren(node) {
|
||||
if (!node.isParent) {
|
||||
return []
|
||||
@@ -248,49 +365,27 @@ export default {
|
||||
if (!children) {
|
||||
return []
|
||||
}
|
||||
let allChildren = []
|
||||
const allChildren = []
|
||||
children.forEach((n) => {
|
||||
allChildren = [...children, ...this.recurseChildren(n)]
|
||||
allChildren.push(n)
|
||||
allChildren.push(...this.recurseChildren(n))
|
||||
})
|
||||
return allChildren
|
||||
},
|
||||
groupBy(array, filter) {
|
||||
const groups = {}
|
||||
array.forEach(function(o) {
|
||||
const group = JSON.stringify(filter(o))
|
||||
groups[group] = groups[group] || []
|
||||
groups[group].push(o)
|
||||
})
|
||||
return Object.keys(groups).map(function(group) {
|
||||
return groups[group]
|
||||
})
|
||||
},
|
||||
filterTree(keyword, tree = this.zTree) {
|
||||
|
||||
searchFromLocal(keyword, tree = this.zTree) {
|
||||
if (!this.zTree) return
|
||||
|
||||
const searchNode = tree.getNodesByFilter((node) => node.id === 'search')
|
||||
if (searchNode) tree.removeNode(searchNode[0])
|
||||
const nodes = tree.transformToArray(tree.getNodes())
|
||||
|
||||
const allNodes = tree.transformToArray(tree.getNodes())
|
||||
if (!keyword) {
|
||||
tree.showNodes(nodes)
|
||||
tree.showNodes(allNodes)
|
||||
tree.expandAll(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (!keyword) {
|
||||
if (tree.hiddenNodes) {
|
||||
tree.showNodes(tree.hiddenNodes)
|
||||
tree.hiddenNodes = null
|
||||
}
|
||||
if (tree.expandNodes) {
|
||||
tree.expandNodes.forEach((node) => {
|
||||
if (node.id !== nodes[0].id) {
|
||||
tree.expandNode(node, false)
|
||||
}
|
||||
})
|
||||
tree.expandNodes = null
|
||||
}
|
||||
return null
|
||||
}
|
||||
let shouldShow = []
|
||||
const matchedNodes = tree.getNodesByFilter((node) => {
|
||||
return node.name.toLowerCase().indexOf(keyword.toLowerCase()) > -1
|
||||
})
|
||||
@@ -300,67 +395,54 @@ export default {
|
||||
const assetsAmount = matchedNodes.length
|
||||
name = `${name} (${assetsAmount})`
|
||||
const newNode = { id: 'search', name: name, isParent: false, open: false }
|
||||
tree.addNodes(null, newNode)
|
||||
const addedNodes = tree.addNodes(null, newNode)
|
||||
// 隐藏所有节点,只显示搜索节点
|
||||
tree.hideNodes(allNodes)
|
||||
tree.showNodes(addedNodes)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取应该展示的节点,以及应该展开的节点
|
||||
let shouldShow = []
|
||||
let shouldExpandNodes = []
|
||||
let shouldCollapseNodes = []
|
||||
matchedNodes.forEach((node) => {
|
||||
const parents = this.recurseParent(node)
|
||||
const children = this.recurseChildren(node)
|
||||
shouldShow = [...shouldShow, ...parents, ...children, node]
|
||||
})
|
||||
// 应该显示匹配节点本身、其祖先节点和子孙节点
|
||||
shouldShow.push(node)
|
||||
shouldShow.push(...parents)
|
||||
shouldShow.push(...children)
|
||||
|
||||
tree.hiddenNodes = nodes
|
||||
tree.expandNodes = shouldShow
|
||||
tree.hideNodes(nodes)
|
||||
// 应该展开匹配节点的父节点,不展开匹配节点的子孙节点
|
||||
shouldExpandNodes.push(...parents)
|
||||
// 应该折叠匹配节点的子孙节点
|
||||
shouldCollapseNodes.push(node)
|
||||
shouldCollapseNodes.push(...children)
|
||||
})
|
||||
shouldShow = Array.from(new Set(shouldShow))
|
||||
shouldExpandNodes = Array.from(new Set(shouldExpandNodes))
|
||||
shouldCollapseNodes = Array.from(new Set(shouldCollapseNodes))
|
||||
|
||||
// 隐藏所有节点,显示应该显示的节点
|
||||
tree.hideNodes(allNodes)
|
||||
tree.showNodes(shouldShow)
|
||||
for (const node of shouldShow) {
|
||||
if (node.isParent) {
|
||||
tree.expandNode(node, true)
|
||||
}
|
||||
// 展开应该展开的节点
|
||||
for (const node of shouldExpandNodes) {
|
||||
tree.expandNode(node, true)
|
||||
}
|
||||
// 折叠应该折叠的节点
|
||||
for (const node of shouldCollapseNodes) {
|
||||
tree.expandNode(node, false)
|
||||
}
|
||||
},
|
||||
filterAssetsServer(keyword) {
|
||||
if (!this.zTree) return
|
||||
let searchNode = this.zTree.getNodesByFilter((node) => node.id === 'search')
|
||||
if (searchNode) {
|
||||
this.zTree.removeChildNodes(searchNode[0])
|
||||
this.zTree.removeNode(searchNode[0])
|
||||
}
|
||||
const treeNodes = this.zTree.getNodes()
|
||||
if (!keyword) {
|
||||
if (treeNodes.length !== 0) {
|
||||
this.zTree.showNodes(treeNodes)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (treeNodes.length !== 0) {
|
||||
this.zTree.hideNodes(treeNodes)
|
||||
}
|
||||
|
||||
let treeUrl = this.treeSetting.searchUrl ? this.treeSetting.searchUrl : this.treeSetting.treeUrl
|
||||
const filterField = treeUrl.includes('?') ? `&search=${keyword}` : `?search=${keyword}`
|
||||
if (treeUrl.indexOf('assets/nodes/children/tree') > -1) {
|
||||
treeUrl = treeUrl + '&all=all'
|
||||
}
|
||||
const searchUrl = `${treeUrl}${filterField}`
|
||||
this.$axios.get(searchUrl).then(nodes => {
|
||||
let name = this.$t('Search')
|
||||
const assetsAmount = nodes.length
|
||||
name = `${name} (${assetsAmount})`
|
||||
const newNode = { id: 'search', name: name, isParent: true, open: true, zAsync: true }
|
||||
searchNode = this.zTree.addNodes(null, newNode)[0]
|
||||
searchNode.zAsync = true
|
||||
this.rootNodeAddDom(searchNode)
|
||||
|
||||
const nodesGroupByOrg = this.groupBy(nodes, (node) => {
|
||||
return node.meta?.data?.org_name
|
||||
})
|
||||
|
||||
for (const item of nodesGroupByOrg) {
|
||||
this.zTree.addNodes(searchNode, item)
|
||||
}
|
||||
searchNode.open = true
|
||||
})
|
||||
searchFromServer(keyword) {
|
||||
// 直接用搜索 API 返回的数据重新初始化树
|
||||
const treeUrl = this.treeSetting.searchUrl ? this.treeSetting.searchUrl : this.treeSetting.treeUrl
|
||||
const searchTypeKey = this.treeSearchTypeOptions[this.treeSearchType]?.search_key || 'search'
|
||||
const searchUrl = setUrlParam(treeUrl, searchTypeKey, keyword)
|
||||
this.initTree(true, searchUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,12 +706,19 @@ div.rMenu li {
|
||||
|
||||
.fixed-tree-search {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
& ::v-deep .el-input__inner {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background: #fafafa;
|
||||
padding-right: 32px;
|
||||
color: var(--color-text-primary)
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
& ::v-deep .el-input__suffix {
|
||||
@@ -653,6 +742,37 @@ div.rMenu li {
|
||||
& ::v-deep .el-input__suffix-inner {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
& ::v-deep .el-input-group__prepend {
|
||||
padding-left: 5px;
|
||||
padding-right: 3px;
|
||||
border: none;
|
||||
color: #999;
|
||||
* {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
background: #fafafa;
|
||||
.el-icon-arrow-down {
|
||||
display: inline-block;
|
||||
transition: transform 0.8s ease; /* 动画关键 */
|
||||
}
|
||||
:hover {
|
||||
.el-icon-arrow-down {
|
||||
transform: rotate(180deg); /* 顺时针 180° */
|
||||
}
|
||||
}
|
||||
.search-label {
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dropdown-menu__item.is-active {
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.icon-refresh {
|
||||
|
||||
@@ -78,6 +78,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -65,6 +65,17 @@ export default {
|
||||
'usingOrgs',
|
||||
'currentViewRoute'
|
||||
]),
|
||||
currentOrgDisplayName() {
|
||||
const currentOrgId = this.currentOrg?.id
|
||||
if (!currentOrgId) {
|
||||
return this.$tc('Select')
|
||||
}
|
||||
const matchedOrg = this.usingOrgs.find(item => item.id === currentOrgId)
|
||||
if (matchedOrg?.name) {
|
||||
return matchedOrg.name
|
||||
}
|
||||
return this.currentOrg.name || this.$tc('Select')
|
||||
},
|
||||
orgActionsGroup() {
|
||||
const orgActions = {
|
||||
label: this.$t('OrganizationList'),
|
||||
@@ -110,11 +121,8 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentOrg: {
|
||||
handler() {
|
||||
this.updateWidth()
|
||||
},
|
||||
deep: true
|
||||
currentOrgDisplayName() {
|
||||
this.updateWidth()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -133,8 +141,9 @@ export default {
|
||||
tempSpan.style.fontWeight = 'normal'
|
||||
tempSpan.style.letterSpacing = 'normal'
|
||||
|
||||
// 获取当前组织名称
|
||||
const orgName = this.currentOrg.name || this.$tc('Select')
|
||||
// 获取当前组织显示名称
|
||||
const orgName = this.currentOrgDisplayName
|
||||
|
||||
tempSpan.textContent = orgName
|
||||
document.body.appendChild(tempSpan)
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__prefix .el-input__icon{
|
||||
.el-input__prefix .el-input__icon {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
}
|
||||
@@ -402,11 +402,9 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* 搜索模态框全局样式 */
|
||||
.search-modal {
|
||||
::v-deep .search-modal {
|
||||
&.el-dialog {
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
@@ -427,17 +425,17 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
body .v-modal {
|
||||
::v-deep body .v-modal {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.search-modal-content {
|
||||
::v-deep .search-modal-content {
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
::v-deep .search-input-wrapper {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
// background: #fff;
|
||||
@@ -451,7 +449,7 @@ body .v-modal {
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
::v-deep .search-results {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -476,7 +474,7 @@ body .v-modal {
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
::v-deep .section-title {
|
||||
padding: 12px 24px 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
@@ -505,12 +503,12 @@ body .v-modal {
|
||||
|
||||
.clear-icon {
|
||||
font-size: 14px;
|
||||
color: red;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
::v-deep .list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -581,15 +579,15 @@ body .v-modal {
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.empty {
|
||||
::v-deep .loading,
|
||||
::v-deep .empty {
|
||||
padding: 32px 24px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.section.placeholder {
|
||||
::v-deep .section.placeholder {
|
||||
padding: 32px 24px;
|
||||
|
||||
.placeholder-content {
|
||||
@@ -637,4 +635,3 @@ body .v-modal {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -167,8 +167,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page.no-title {
|
||||
::v-deep {
|
||||
.page-submenu .el-tabs__header {
|
||||
@@ -199,6 +198,14 @@ export default {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-next {
|
||||
|
||||
@@ -276,10 +276,8 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
//reset element css of el-icon-close
|
||||
// reset element css of el-icon-close
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
.el-icon-close {
|
||||
|
||||
@@ -149,6 +149,11 @@ export function getErrorResponseMsg(error) {
|
||||
.join('; ')
|
||||
} else if (typeof data === 'string') {
|
||||
return data
|
||||
} else if (_.isPlainObject(data)) {
|
||||
return Object.values(data)
|
||||
.map(item => getErrorResponseMsg(item))
|
||||
.filter(i => i)
|
||||
.join('; ')
|
||||
} else {
|
||||
msg = error.toString()
|
||||
}
|
||||
|
||||
@@ -144,11 +144,6 @@ export default {
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.$store.getters.hasValidLicense) {
|
||||
delete this.fields[3]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAfterGetRemoteMeta(meta) {
|
||||
const needSetOptionFields = [
|
||||
|
||||
@@ -43,7 +43,9 @@ export default {
|
||||
asset: ''
|
||||
},
|
||||
treeSetting: {
|
||||
showMenu: true,
|
||||
showMenu: (node) => {
|
||||
return node?.meta?.type === 'asset'
|
||||
},
|
||||
showRefresh: true,
|
||||
showSearch: true,
|
||||
showAssets: true,
|
||||
@@ -55,7 +57,7 @@ export default {
|
||||
menu: [
|
||||
{
|
||||
id: 'check',
|
||||
name: this.$t('Check'),
|
||||
name: this.$t('RiskDetection'),
|
||||
icon: 'scan',
|
||||
callback: (node) => {
|
||||
vm.detectDialog.asset = node.id
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
class="risk-review-drawer"
|
||||
destroy-on-close
|
||||
direction="rtl"
|
||||
style="z-index: 999"
|
||||
>
|
||||
<div class="drawer-container">
|
||||
<div class="drawer-body">
|
||||
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
updateSuccessNextRoute: this.updateSuccessNextRoute,
|
||||
hasDetailInMsg: false,
|
||||
fields: [
|
||||
[this.$t('Basic'), ['name', 'address', 'platform', 'nodes']],
|
||||
[this.$t('Basic'), ['name', 'address', 'platform', 'node']],
|
||||
[this.$t('Protocol'), ['protocols']],
|
||||
[this.$t('Account'), ['accounts']],
|
||||
[this.$t('Other'), ['directory_services', 'zone', 'labels', 'is_active', 'comment']]
|
||||
@@ -73,8 +73,8 @@ export default {
|
||||
const values = _.cloneDeep(validValues)
|
||||
const submitMethod = id ? 'put' : 'post'
|
||||
|
||||
if (values.nodes && values.nodes.length === 0) {
|
||||
delete values['nodes']
|
||||
if (!values.node) {
|
||||
delete values['node']
|
||||
}
|
||||
|
||||
if (submitMethod === 'put') {
|
||||
@@ -143,15 +143,14 @@ export default {
|
||||
},
|
||||
async setInitial() {
|
||||
const { defaultConfig } = this
|
||||
const { node } = this.$route.query
|
||||
const nodesInitial = node ? [node] : []
|
||||
const { node_id } = this.$route.query
|
||||
const platformId = this.platformID || 'Linux'
|
||||
const url = `/api/v1/assets/platforms/${platformId}/`
|
||||
this.platform = await this.$axios.get(url)
|
||||
const initial = {
|
||||
labels: [],
|
||||
is_active: true,
|
||||
nodes: nodesInitial,
|
||||
node: node_id,
|
||||
platform: parseInt(this.platform.id),
|
||||
protocols: []
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ export default {
|
||||
url: '/api/v1/assets/assets/',
|
||||
showMenu: !this.$store.getters.currentOrgIsRoot,
|
||||
showDefaultMenu: true,
|
||||
async: {
|
||||
enable: false
|
||||
},
|
||||
menu: [
|
||||
]
|
||||
},
|
||||
|
||||
@@ -143,7 +143,20 @@ export default {
|
||||
disabled: !this.canEdit
|
||||
},
|
||||
callbacks: Object.freeze({
|
||||
click: () => {
|
||||
click: async () => {
|
||||
try {
|
||||
await this.$confirm(
|
||||
this.$t('overwriteProtocolsAndPortsMsg'),
|
||||
this.$t('Confirm'),
|
||||
{
|
||||
confirmButtonText: this.$t('Confirm'),
|
||||
cancelButtonText: this.$t('Cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
const data = { platform_id: this.object.id }
|
||||
this.$axios.post(
|
||||
'/api/v1/assets/assets/sync-platform-protocols/', data).then(res => {
|
||||
|
||||
@@ -148,9 +148,10 @@ export const assetFieldsMeta = (vm, category, type) => {
|
||||
return vm.platform.ds_enabled === false
|
||||
}
|
||||
},
|
||||
nodes: {
|
||||
node: {
|
||||
rules: [rules.RequiredChange],
|
||||
el: {
|
||||
multiple: false,
|
||||
ajax: {
|
||||
url: '/api/v1/assets/nodes/',
|
||||
transformOption: item => {
|
||||
|
||||
@@ -125,17 +125,21 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
/* 修复input 背景不协调 和光标变色 */
|
||||
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
|
||||
|
||||
$bg:#283443;
|
||||
$light_gray:#fff;
|
||||
$cursor: #fff;
|
||||
$input-bg: #283443;
|
||||
$input-light-gray: #fff;
|
||||
$input-cursor: #fff;
|
||||
|
||||
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
|
||||
$login-bg: #2d3a4b;
|
||||
$login-dark-gray: #889aa4;
|
||||
$login-light-gray: #eee;
|
||||
|
||||
@supports (-webkit-mask: none) and (not (cater-color: $input-cursor)) {
|
||||
.login-container .el-input input {
|
||||
color: $cursor;
|
||||
color: $input-cursor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,17 +151,17 @@ $cursor: #fff;
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border: 0px;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0px;
|
||||
border-radius: 0;
|
||||
padding: 12px 5px 12px 15px;
|
||||
color: $light_gray;
|
||||
color: $input-light-gray;
|
||||
height: 47px;
|
||||
caret-color: $cursor;
|
||||
caret-color: $input-cursor;
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0px 1000px $bg inset !important;
|
||||
-webkit-text-fill-color: $cursor !important;
|
||||
box-shadow: 0 0 0 1000px $input-bg inset !important;
|
||||
-webkit-text-fill-color: $input-cursor !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,17 +173,11 @@ $cursor: #fff;
|
||||
color: #454545;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$bg:#2d3a4b;
|
||||
$dark_gray:#889aa4;
|
||||
$light_gray:#eee;
|
||||
|
||||
.login-container {
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
background-color: $bg;
|
||||
background-color: $login-bg;
|
||||
overflow: hidden;
|
||||
|
||||
.login-form {
|
||||
@@ -205,7 +203,7 @@ $light_gray:#eee;
|
||||
|
||||
.svg-container {
|
||||
padding: 6px 5px 6px 15px;
|
||||
color: $dark_gray;
|
||||
color: $login-dark-gray;
|
||||
vertical-align: middle;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
@@ -216,7 +214,7 @@ $light_gray:#eee;
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
color: $light_gray;
|
||||
color: $login-light-gray;
|
||||
margin: 0px auto 40px auto;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
@@ -228,7 +226,7 @@ $light_gray:#eee;
|
||||
right: 10px;
|
||||
top: 7px;
|
||||
font-size: 16px;
|
||||
color: $dark_gray;
|
||||
color: $login-dark-gray;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -515,6 +515,8 @@ export default {
|
||||
this.setBtn()
|
||||
this.selectAssets = assets
|
||||
this.selectNodes = nodes
|
||||
}).catch(() => {
|
||||
this.lastRequestPayload = null
|
||||
})
|
||||
},
|
||||
viewConfirmRunAssets() {
|
||||
@@ -525,8 +527,8 @@ export default {
|
||||
.then(() => {
|
||||
this.xterm.write(
|
||||
'\x1b[31m' +
|
||||
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) +
|
||||
'\x1b[0m'
|
||||
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) +
|
||||
'\x1b[0m'
|
||||
)
|
||||
this.xterm.write(this.wrapperError(''))
|
||||
this.getTaskStatus()
|
||||
|
||||
@@ -67,28 +67,28 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.help-dialog.dialog .el-dialog__footer {
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .help-dialog.dialog .el-dialog__footer {
|
||||
border-top: none;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
&::v-deep th, td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
::v-deep .help-table th,
|
||||
::v-deep .help-table td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&::v-deep .item-td, .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
::v-deep .help-table .item-td,
|
||||
::v-deep .help-table .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,66 +1,72 @@
|
||||
<template>
|
||||
<TwoCol>
|
||||
<template>
|
||||
<AutoDetailCard
|
||||
:excludes="excludes"
|
||||
:fields="detailFields"
|
||||
:object="object"
|
||||
:url="url"
|
||||
/>
|
||||
<AutoDetailCard :excludes="excludes" :fields="detailFields" :object="object" :url="url" />
|
||||
</template>
|
||||
<template v-if="hasSummary" #right class="detail-right-quick-actions">
|
||||
<IBox
|
||||
v-if="object.summary.ok"
|
||||
:title="`${$tc('SuccessAsset')} (${object.summary.ok.length})` "
|
||||
:title="`${$tc('SuccessAsset')} (${object.summary.ok.length})`"
|
||||
type="success"
|
||||
>
|
||||
<el-collapse>
|
||||
<el-collapse-item
|
||||
v-for="(item,index) in object.summary.ok"
|
||||
v-for="(item, index) in object.summary.ok"
|
||||
:key="index"
|
||||
:name="index"
|
||||
:title="item"
|
||||
disabled
|
||||
/>
|
||||
>
|
||||
<template #title>
|
||||
<el-tooltip :content="item" placement="top" class="text-overflow">
|
||||
<span>{{ item }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</IBox>
|
||||
<IBox
|
||||
v-if="object.summary.excludes"
|
||||
:title="`${$tc('ExcludeAsset')} (${Object.keys(object.summary.excludes).length})` "
|
||||
:title="`${$tc('ExcludeAsset')} (${Object.keys(object.summary.excludes).length})`"
|
||||
type="warning"
|
||||
>
|
||||
<el-collapse>
|
||||
<el-collapse-item
|
||||
v-for="(val,key,index) in object.summary.excludes"
|
||||
v-for="(val, key, index) in object.summary.excludes"
|
||||
:key="index"
|
||||
:name="index"
|
||||
:title="key"
|
||||
>
|
||||
<template #title>
|
||||
<el-tooltip :content="key" placement="top" class="text-overflow">
|
||||
<span>{{ key }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<div>{{ $tc('Reason') }}: {{ val }}</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</IBox>
|
||||
<IBox
|
||||
v-if="object.summary.failures"
|
||||
:title="`${$tc('FailedAsset')} (${Object.keys(Object.assign(object.summary.failures,object.summary.dark)).length})` "
|
||||
:title="`${$tc('FailedAsset')} (${Object.keys(Object.assign(object.summary.failures, object.summary.dark)).length})`"
|
||||
type="danger"
|
||||
>
|
||||
<el-collapse>
|
||||
<el-collapse-item
|
||||
v-for="(val,key,index) in Object.assign(object.summary.failures,object.summary.dark)"
|
||||
v-for="(val, key, index) in Object.assign(object.summary.failures, object.summary.dark)"
|
||||
:key="index"
|
||||
:name="index"
|
||||
:title="key"
|
||||
>
|
||||
<template #title>
|
||||
<el-tooltip :content="key" placement="top" class="text-overflow">
|
||||
<span>{{ key }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<div>{{ $tc('Reason') }}: {{ val }}</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</IBox>
|
||||
<IBox
|
||||
v-if="object.summary.error"
|
||||
:title="$tc('SystemError') "
|
||||
type="danger"
|
||||
>
|
||||
<IBox v-if="object.summary.error" :title="$tc('SystemError')" type="danger">
|
||||
{{ object.summary.error }}
|
||||
</IBox>
|
||||
</template>
|
||||
@@ -86,11 +92,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
excludes: [
|
||||
'job', 'parameters', 'summary', 'task_id', 'timedelta'
|
||||
],
|
||||
excludes: ['job', 'parameters', 'summary', 'task_id', 'timedelta'],
|
||||
detailFields: [
|
||||
'task_id', 'time_cost',
|
||||
'task_id',
|
||||
'time_cost',
|
||||
{
|
||||
key: this.$t('IsFinished'),
|
||||
value: this.object.is_finished ? this.$t('Yes') : this.$t('No')
|
||||
@@ -99,8 +104,12 @@ export default {
|
||||
key: this.$t('IsSuccess'),
|
||||
value: this.object.is_success ? this.$t('Yes') : this.$t('No')
|
||||
},
|
||||
'job_type', 'material', 'org_name',
|
||||
'date_start', 'date_finished', 'date_created'
|
||||
'job_type',
|
||||
'material',
|
||||
'org_name',
|
||||
'date_start',
|
||||
'date_finished',
|
||||
'date_created'
|
||||
],
|
||||
url: `/api/v1/ops/job-executions/${this.object.id}/`
|
||||
}
|
||||
|
||||
@@ -408,7 +408,6 @@ export default {
|
||||
createJob(data).then(res => {
|
||||
this.progressLength = 0
|
||||
this.executionInfo.timeCost = 0
|
||||
this.showProgress = true
|
||||
this.speedText = ''
|
||||
const form = new FormData()
|
||||
const start = Date.now()
|
||||
@@ -436,13 +435,13 @@ export default {
|
||||
}
|
||||
}
|
||||
}).then(res => {
|
||||
this.showProgress = true
|
||||
this.executionInfo.status = 'running'
|
||||
this.currentTaskId = res.task_id
|
||||
this.xtermConfig = { taskId: this.currentTaskId, type: 'shortcut_cmd' }
|
||||
this.setCostTimeInterval()
|
||||
this.writeExecutionOutput()
|
||||
}).catch((error) => {
|
||||
this.$message.error(this.$tc('Error'), error)
|
||||
}).catch(() => {
|
||||
this.execute_stop()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -224,11 +224,12 @@ export default {
|
||||
title: this.$t('ExecuteAfterSaving'),
|
||||
callback: (value, form, btn) => {
|
||||
form.value.run_after_save = true
|
||||
const parameters = form.value.variable.reduce((acc, item) => {
|
||||
acc[item.var_name] = item.default_value || ''
|
||||
return acc
|
||||
}, {})
|
||||
form.value['parameters'] = parameters
|
||||
if (form.value?.variable) {
|
||||
form.value['parameters'] = form.value.variable.reduce((acc, item) => {
|
||||
acc[item.var_name] = item.default_value || ''
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
this.submitForm(form, btn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,8 @@ export default {
|
||||
type: 'input',
|
||||
component: AccountFormatter,
|
||||
el: {
|
||||
enableExcludeAccounts: true,
|
||||
enableNoneAccount: true,
|
||||
assets: [],
|
||||
nodes: []
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="realRadioSelected" @input="handleRadioChanged">
|
||||
<el-radio
|
||||
v-for="(i) in realChoices"
|
||||
v-for="(i) in iRealChoices"
|
||||
:key="i.label"
|
||||
:disabled="i.disabled"
|
||||
:label="i.value"
|
||||
@@ -125,6 +125,14 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
enableNoneAccount: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enableExcludeAccounts: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
addTemplateHelpText: {
|
||||
type: String,
|
||||
default() {
|
||||
@@ -185,6 +193,18 @@ export default {
|
||||
computed: {
|
||||
virtualAccount() {
|
||||
return virtualAccount
|
||||
},
|
||||
iRealChoices: {
|
||||
get() {
|
||||
let choices = this.realChoices.slice()
|
||||
if (!this.enableNoneAccount) {
|
||||
choices = choices.filter(i => i.value !== NoneAccount)
|
||||
}
|
||||
if (!this.enableExcludeAccounts) {
|
||||
choices = choices.filter(i => i.value !== ExcludeAccount)
|
||||
}
|
||||
return choices
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -32,6 +32,22 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordMenuDisabled() {
|
||||
return this.$store.state.users.profile.source.value !== 'local'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
passwordMenuDisabled: {
|
||||
immediate: true,
|
||||
handler(disabled) {
|
||||
this.config.submenu = this.getSubmenu()
|
||||
if (disabled && this.config.activeMenu === 'Password') {
|
||||
this.config.activeMenu = 'SSHKeyList'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSubmenu() {
|
||||
return [
|
||||
|
||||
@@ -222,13 +222,11 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-select-dropdown.select-org-dropdown {
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .el-select-dropdown.select-org-dropdown {
|
||||
max-width: 300px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.org-select {
|
||||
float: left;
|
||||
width: 300px;
|
||||
@@ -245,5 +243,4 @@ export default {
|
||||
width: 180px !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { GenericCreateUpdateForm } from '@/layout/components'
|
||||
import IBox from '@/components/Common/IBox/index.vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
import ChatProvidersField from './components/ChatProvidersField.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -17,106 +18,54 @@ export default {
|
||||
GenericCreateUpdateForm
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
const hasProviders = (formValue) => {
|
||||
const providers = formValue?.CHAT_AI_PROVIDERS?.providers || []
|
||||
return Array.isArray(providers) && providers.length > 0
|
||||
}
|
||||
return {
|
||||
url: '/api/v1/settings/setting/?category=chat',
|
||||
hasReset: false,
|
||||
moreButtons: [
|
||||
{
|
||||
title: this.$t('Test'),
|
||||
loading: false,
|
||||
callback: function(value, form, btn) {
|
||||
btn.loading = true
|
||||
vm.$axios.post(
|
||||
'/api/v1/settings/chatai/testing/',
|
||||
value
|
||||
).then(res => {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => {
|
||||
btn.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: ['VAULT_HCP_TOKEN'],
|
||||
fields: [
|
||||
'CHAT_AI_ENABLED',
|
||||
'CHAT_AI_METHOD',
|
||||
'CHAT_AI_EMBED_URL',
|
||||
'CHAT_AI_TYPE',
|
||||
'DEEPSEEK_BASE_URL',
|
||||
'DEEPSEEK_API_KEY',
|
||||
'DEEPSEEK_PROXY',
|
||||
'DEEPSEEK_MODEL',
|
||||
'GPT_BASE_URL',
|
||||
'GPT_API_KEY',
|
||||
'GPT_PROXY',
|
||||
'GPT_MODEL'
|
||||
'CHAT_AI_PROVIDERS',
|
||||
'CHAT_AI_EMBED_URL'
|
||||
],
|
||||
fieldsMeta: {
|
||||
CHAT_AI_TYPE: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api'
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || hasProviders(formValue)
|
||||
}
|
||||
},
|
||||
GPT_BASE_URL: {
|
||||
el: {
|
||||
autocomplete: 'new-password'
|
||||
},
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
||||
}
|
||||
},
|
||||
GPT_API_KEY: {
|
||||
el: {
|
||||
autocomplete: 'new-password'
|
||||
},
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
||||
}
|
||||
},
|
||||
GPT_PROXY: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
||||
}
|
||||
},
|
||||
GPT_MODEL: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'gpt'
|
||||
}
|
||||
},
|
||||
DEEPSEEK_BASE_URL: {
|
||||
el: {
|
||||
autocomplete: 'new-password'
|
||||
},
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
||||
}
|
||||
},
|
||||
DEEPSEEK_API_KEY: {
|
||||
el: {
|
||||
autocomplete: 'new-password'
|
||||
},
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
||||
}
|
||||
},
|
||||
DEEPSEEK_PROXY: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
||||
}
|
||||
},
|
||||
DEEPSEEK_MODEL: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.CHAT_AI_METHOD !== 'api' || formValue.CHAT_AI_TYPE !== 'deep-seek'
|
||||
}
|
||||
CHAT_AI_PROVIDERS: {
|
||||
component: ChatProvidersField,
|
||||
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'api'
|
||||
},
|
||||
CHAT_AI_EMBED_URL: {
|
||||
hidden: (formValue) => formValue.CHAT_AI_METHOD !== 'embed'
|
||||
}
|
||||
},
|
||||
afterGetFormValue(formValue) {
|
||||
const providers = Array.isArray(formValue.CHAT_AI_PROVIDERS) ? formValue.CHAT_AI_PROVIDERS : []
|
||||
return {
|
||||
...formValue,
|
||||
CHAT_AI_PROVIDERS: {
|
||||
providers: providers
|
||||
}
|
||||
}
|
||||
},
|
||||
cleanFormValue(values) {
|
||||
const config = values.CHAT_AI_PROVIDERS || {}
|
||||
const providers = Array.isArray(config.providers) ? config.providers : []
|
||||
return {
|
||||
...values,
|
||||
CHAT_AI_PROVIDERS: providers,
|
||||
CHAT_AI_TYPE: values.CHAT_AI_TYPE || providers[0]?.type || values.CHAT_AI_TYPE
|
||||
}
|
||||
},
|
||||
submitMethod() {
|
||||
return 'patch'
|
||||
}
|
||||
|
||||
186
src/views/settings/Feature/components/ChatProvidersField.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="providers-card">
|
||||
<div class="providers-header">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="addProvider">
|
||||
{{ $t('Add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="localProviders" size="mini" border>
|
||||
<el-table-column :label="$t('Type')" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.type" size="mini" @change="emitChange">
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Base URL')" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.base_url"
|
||||
size="mini"
|
||||
:placeholder="openAIBaseUrl"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('API Key')" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.api_key"
|
||||
size="mini"
|
||||
show-password
|
||||
autocomplete="new-password"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Proxy')" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.proxy"
|
||||
size="mini"
|
||||
placeholder="http://ip:port"
|
||||
@input="emitChange"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Actions')" width="100" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="danger-text"
|
||||
icon="el-icon-delete"
|
||||
@click="removeProvider($index)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
export default {
|
||||
name: 'ChatProvidersField',
|
||||
props: {
|
||||
value: {
|
||||
type: [Object, Array],
|
||||
default: () => ({ providers: [], defaultProvider: '' })
|
||||
},
|
||||
typeOptions: {
|
||||
type: Array,
|
||||
default: () => ([
|
||||
{ label: 'Ollama', value: 'ollama' },
|
||||
{ label: 'OpenAI', value: 'openai' }
|
||||
])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const { providers, defaultProvider } = this.normalizeValue(this.value)
|
||||
return {
|
||||
localProviders: providers,
|
||||
defaultProvider: this.pickDefault(defaultProvider, providers),
|
||||
openAIBaseUrl: 'https://api.openai.com/v1'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(v) {
|
||||
const { providers, defaultProvider } = this.normalizeValue(v)
|
||||
this.localProviders = providers
|
||||
this.defaultProvider = this.pickDefault(defaultProvider, providers) || ''
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emptyProvider() {
|
||||
return {
|
||||
type: 'openai',
|
||||
base_url: this.openAIBaseUrl,
|
||||
api_key: '',
|
||||
proxy: ''
|
||||
}
|
||||
},
|
||||
normalizeValue(v) {
|
||||
if (!v) {
|
||||
return { providers: [], defaultProvider: '' }
|
||||
}
|
||||
// allow legacy array value
|
||||
if (Array.isArray(v)) {
|
||||
return { providers: cloneDeep(v), defaultProvider: '' }
|
||||
}
|
||||
const providers = Array.isArray(v.providers) ? cloneDeep(v.providers) : []
|
||||
return { providers, defaultProvider: v.defaultProvider || '' }
|
||||
},
|
||||
pickDefault(current, providers) {
|
||||
const enabledProviders = providers.filter(item => item?.enabled !== false)
|
||||
if (current && providers.some(item => item.name === current)) {
|
||||
return current
|
||||
}
|
||||
return enabledProviders[0]?.name || providers[0]?.name || ''
|
||||
},
|
||||
emitChange() {
|
||||
const providers = cloneDeep(this.localProviders || [])
|
||||
const defaultProvider = this.pickDefault(this.defaultProvider, providers)
|
||||
this.defaultProvider = defaultProvider
|
||||
this.$emit('input', { providers, defaultProvider })
|
||||
this.$emit('change', { providers, defaultProvider })
|
||||
},
|
||||
handleAssistantChange(current) {
|
||||
this.emitChange()
|
||||
},
|
||||
addProvider() {
|
||||
this.localProviders.push(this.emptyProvider())
|
||||
this.emitChange()
|
||||
},
|
||||
removeProvider(index) {
|
||||
this.localProviders.splice(index, 1)
|
||||
this.emitChange()
|
||||
},
|
||||
setDefault(name) {
|
||||
this.defaultProvider = name
|
||||
this.emitChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.providers-card {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.providers-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: #909399;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
@@ -73,28 +73,28 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.help-dialog.dialog .el-dialog__footer {
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .help-dialog.dialog .el-dialog__footer {
|
||||
border-top: none;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
&::v-deep th, td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
::v-deep .help-table th,
|
||||
::v-deep .help-table td {
|
||||
height: 40px;
|
||||
padding: 0 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&::v-deep .item-td, .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
::v-deep .help-table .item-td,
|
||||
::v-deep .help-table .item-label {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -219,11 +219,12 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-popover.el-popper.monitor-popover {
|
||||
<style lang="scss" scoped>
|
||||
:global(.el-popover.el-popper.monitor-popover) {
|
||||
ul {
|
||||
padding-left: 15px;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
line-height: 24px;
|
||||
color: var(--color-primary);
|
||||
@@ -231,19 +232,19 @@ export default {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
filter: opacity(65%)!important;
|
||||
filter: opacity(65%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.echarts {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
@@ -256,6 +257,7 @@ export default {
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.num {
|
||||
margin-left: 2px;
|
||||
cursor: pointer;
|
||||
@@ -279,9 +281,9 @@ export default {
|
||||
line-height: 20px;
|
||||
padding: 2px 0;
|
||||
color: #fff;
|
||||
-webkit-transition: width .6s ease;
|
||||
-o-transition: width .6s ease;
|
||||
transition: width .6s ease;
|
||||
-webkit-transition: width 0.6s ease;
|
||||
-o-transition: width 0.6s ease;
|
||||
transition: width 0.6s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -292,9 +294,11 @@ export default {
|
||||
margin-right: 5px;
|
||||
border-radius: 4px;
|
||||
background-color: currentColor;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -303,6 +307,7 @@ export default {
|
||||
.session {
|
||||
margin-top: 13px;
|
||||
border-left: 1px solid #f3f3f3;
|
||||
|
||||
.session-title {
|
||||
margin-bottom: 8px;
|
||||
color: #a3a3a4;
|
||||
|
||||
@@ -147,7 +147,7 @@ export default {
|
||||
this.loading = true
|
||||
const url = `/api/v1/tickets/comments/?ticket_id=${this.object.id}`
|
||||
this.$axios.get(url).then(res => {
|
||||
this.comments = res.results
|
||||
this.comments = res
|
||||
}).catch(err => {
|
||||
this.$message.error(err)
|
||||
}).finally(() => {
|
||||
|
||||
@@ -115,14 +115,12 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.acceptance .el-message-box__content {
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .acceptance .el-message-box__content {
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.box {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
treeUrl: `/api/v1/perms/users/${this.object.id}/nodes/children/tree/`,
|
||||
tableUrl: `/api/v1/perms/users/${this.object.id}/assets/?all=1`,
|
||||
tableUrl: `/api/v1/perms/users/${this.object.id}/assets/`,
|
||||
actions: {
|
||||
has: false
|
||||
},
|
||||
|
||||
@@ -161,8 +161,7 @@ module.exports = {
|
||||
target: process.env.VUE_APP_CORE_HOST || 'http://127.0.0.1:8080',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
after: require('./mock/mock-server.js')
|
||||
}
|
||||
},
|
||||
css: {},
|
||||
configureWebpack: {
|
||||
|
||||
87
yarn.lock
@@ -1069,6 +1069,11 @@
|
||||
resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
|
||||
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
|
||||
|
||||
"@soda/friendly-errors-webpack-plugin@^1.7.1":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.npmmirror.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz#4d4fbb1108993aaa362116247c3d18188a2c6c85"
|
||||
@@ -4767,6 +4772,13 @@ debug@^3.1.0, debug@^3.2.7:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debuglog@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||
@@ -5301,6 +5313,22 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@~6.6.1:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.3.tgz#815393fa24f30b8e6afa8f77ccca2f28146be6de"
|
||||
integrity sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.2.1"
|
||||
ws "~8.17.1"
|
||||
xmlhttprequest-ssl "~2.1.1"
|
||||
|
||||
engine.io-parser@~5.2.1:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
|
||||
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
|
||||
|
||||
enhanced-resolve@^4.1.0, enhanced-resolve@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec"
|
||||
@@ -6916,7 +6944,7 @@ hasown@^2.0.0, hasown@^2.0.2:
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
he@1.2.x, he@^1.1.0, he@^1.1.1:
|
||||
he@1.2.x, he@^1.1.1, he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
@@ -12884,6 +12912,24 @@ snapdragon@^0.8.1:
|
||||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
socket.io-client@^4.8.1:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb"
|
||||
integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.2"
|
||||
engine.io-client "~6.6.1"
|
||||
socket.io-parser "~4.2.4"
|
||||
|
||||
socket.io-parser@~4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
|
||||
integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
sockjs-client@^1.5.0:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.npmmirror.com/sockjs-client/-/sockjs-client-1.6.1.tgz#350b8eda42d6d52ddc030c39943364c11dcad806"
|
||||
@@ -14208,16 +14254,16 @@ utils-merge@1.0.1:
|
||||
resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
uuid@8.3.2, uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v-sanitize@^0.0.13:
|
||||
version "0.0.13"
|
||||
resolved "https://registry.npmmirror.com/v-sanitize/-/v-sanitize-0.0.13.tgz#d98e3eee3277a2b3a632bc2ba119c088967da0c4"
|
||||
@@ -14423,32 +14469,27 @@ vue-style-loader@^4.1.0:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
|
||||
integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
|
||||
vue-template-compiler@2.7.16:
|
||||
version "2.7.16"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b"
|
||||
integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
he "^1.2.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue@2.6.10:
|
||||
version "2.6.10"
|
||||
resolved "https://registry.npmmirror.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
|
||||
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
|
||||
|
||||
vue@2.6.11:
|
||||
version "2.6.11"
|
||||
resolved "https://registry.npmmirror.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
|
||||
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
|
||||
|
||||
vue@^2.2.6:
|
||||
vue@2.7.16, vue@^2.2.6:
|
||||
version "2.7.16"
|
||||
resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9"
|
||||
integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==
|
||||
dependencies:
|
||||
"@vue/compiler-sfc" "2.7.16"
|
||||
@@ -14945,11 +14986,21 @@ ws@^6.0.0, ws@^6.2.1:
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@~8.17.1:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
||||
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
|
||||
|
||||
xmlhttprequest-ssl@~2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23"
|
||||
integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==
|
||||
|
||||
xss@^1.0.14, xss@^1.0.9:
|
||||
version "1.0.15"
|
||||
resolved "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a"
|
||||
|
||||