mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-13 19:35:24 +00:00
Compare commits
30 Commits
perf_termi
...
pr@dev@per
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e678a81ea | ||
|
|
eb4aebf637 | ||
|
|
91eb4b1c0d | ||
|
|
8614798362 | ||
|
|
405054034b | ||
|
|
d8766e1bdb | ||
|
|
6b11d284da | ||
|
|
12b41f5abb | ||
|
|
398c8b0eb6 | ||
|
|
357f68b3d2 | ||
|
|
5d74b0cd84 | ||
|
|
d72671d2c9 | ||
|
|
8f17fa82a2 | ||
|
|
111957b6e6 | ||
|
|
aa3292f988 | ||
|
|
cc0a72cecc | ||
|
|
fae8b57456 | ||
|
|
f2c3beff13 | ||
|
|
03790194ac | ||
|
|
2b5aebbc1b | ||
|
|
6183f38d8a | ||
|
|
29567c5392 | ||
|
|
43f9b8cf68 | ||
|
|
783070c74d | ||
|
|
59d6df5dce | ||
|
|
5fc93f61a2 | ||
|
|
abee8aa7c9 | ||
|
|
18d5194c38 | ||
|
|
4f794d299a | ||
|
|
623499b13b |
@@ -1,4 +1,4 @@
|
|||||||
FROM jumpserver/lina-base:20250508_085854 AS stage-build
|
FROM jumpserver/lina-base:20250616_083043 AS stage-build
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ENV VERSION=$VERSION
|
ENV VERSION=$VERSION
|
||||||
|
|||||||
@@ -76,7 +76,6 @@
|
|||||||
"vue-i18n": "^8.15.5",
|
"vue-i18n": "^8.15.5",
|
||||||
"vue-json-editor": "^1.4.3",
|
"vue-json-editor": "^1.4.3",
|
||||||
"vue-markdown": "^2.2.4",
|
"vue-markdown": "^2.2.4",
|
||||||
"vue-moment": "^4.1.0",
|
|
||||||
"vue-password-strength-meter": "^1.7.2",
|
"vue-password-strength-meter": "^1.7.2",
|
||||||
"vue-router": "3.0.6",
|
"vue-router": "3.0.6",
|
||||||
"vue-select": "^3.9.5",
|
"vue-select": "^3.9.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="chat-action">
|
<div v-if="hasPrompt" class="chat-action">
|
||||||
<Select2
|
<Select2
|
||||||
v-model="select.value"
|
v-model="select.value"
|
||||||
:disabled="isLoading || isSelectDisabled"
|
:disabled="isLoading || isSelectDisabled"
|
||||||
@@ -36,6 +36,10 @@ export default {
|
|||||||
expanded: {
|
expanded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
hasPrompt: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<!-- eslint-disable-next-line -->
|
<!-- eslint-disable-next-line -->
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<p>
|
<p>
|
||||||
<MessageText :message="item.reasoning" />
|
<MessageText :message="item.reasoning" @insert-code="handleInsertCode" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,8 +41,7 @@
|
|||||||
<span v-if="isServerError" class="error">
|
<span v-if="isServerError" class="error">
|
||||||
{{ isServerError }}
|
{{ isServerError }}
|
||||||
</span>
|
</span>
|
||||||
<MessageText :message="item.result" />
|
<MessageText :message="item.result" :is-terminal="isTerminal" @insert-code="handleInsertCode" /></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
@@ -93,6 +92,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTerminal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -142,6 +145,9 @@ export default {
|
|||||||
if (value === 'copy') {
|
if (value === 'copy') {
|
||||||
copy(this.item.result.content)
|
copy(this.item.result.content)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
handleInsertCode(code) {
|
||||||
|
this.$emit('insert-code', code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isTerminal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -45,10 +49,10 @@ export default {
|
|||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
this.addCopyEvents()
|
this.addEvents()
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.removeCopyEvents()
|
this.removeEvents()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
@@ -69,26 +73,64 @@ export default {
|
|||||||
this.markdown.use(mdKatex, { blockClass: 'katexmath-block rounded-md', errorColor: ' #cc0000' })
|
this.markdown.use(mdKatex, { blockClass: 'katexmath-block rounded-md', errorColor: ' #cc0000' })
|
||||||
},
|
},
|
||||||
highlightBlock(str, lang) {
|
highlightBlock(str, lang) {
|
||||||
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${'Copy'}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
|
let insertSpanHtml = `<span class="code-block-header__insert">${this.$t('Insert')}</span>`
|
||||||
|
if (!this.isTerminal) {
|
||||||
|
insertSpanHtml = ''
|
||||||
|
}
|
||||||
|
return `<pre class="code-block-wrapper">
|
||||||
|
<div class="code-block-header">
|
||||||
|
<span class="code-block-header__lang">${lang}</span>
|
||||||
|
<span class="code-block-header__actions">
|
||||||
|
${insertSpanHtml}
|
||||||
|
<span class="code-block-header__copy">${this.$t('Copy')}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<code class="hljs code-block-body ${lang}">${str}</code></pre>`
|
||||||
},
|
},
|
||||||
addCopyEvents() {
|
addEvents() {
|
||||||
const copyBtn = document.querySelectorAll('.code-block-header__copy')
|
this.addBtnClickEvents('.code-block-header__copy', this.handlerClickCopy)
|
||||||
copyBtn.forEach((btn) => {
|
this.addBtnClickEvents('.code-block-header__insert', this.handlerClickInsert)
|
||||||
btn.addEventListener('click', () => {
|
},
|
||||||
const code = btn.parentElement?.nextElementSibling?.textContent
|
|
||||||
if (code) {
|
handlerClickCopy(event) {
|
||||||
copy(code)
|
const wrapper = event.target.closest('.code-block-wrapper')
|
||||||
}
|
if (wrapper) {
|
||||||
|
// 查找里面的 code 元素
|
||||||
|
const codeElement = wrapper.querySelector('code.code-block-body')
|
||||||
|
if (codeElement) {
|
||||||
|
const codeText = codeElement.textContent
|
||||||
|
copy(codeText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlerClickInsert(event) {
|
||||||
|
const wrapper = event.target.closest('.code-block-wrapper')
|
||||||
|
if (wrapper) {
|
||||||
|
// 查找里面的 code 元素
|
||||||
|
const codeElement = wrapper.querySelector('code.code-block-body')
|
||||||
|
if (codeElement) {
|
||||||
|
const codeText = codeElement.textContent
|
||||||
|
this.$emit('insert-code', codeText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addBtnClickEvents(selector, callback) {
|
||||||
|
const buttons = this.$refs.textRef.querySelectorAll(selector)
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
btn.addEventListener('click', callback)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeBtnClickEvent(selector) {
|
||||||
|
const buttons = this.$refs.textRef.querySelectorAll(selector)
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
btn.removeEventListener('click', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeCopyEvents() {
|
removeEvents() {
|
||||||
if (this.$refs.textRef) {
|
if (this.$refs.textRef) {
|
||||||
const copyBtn = this.$refs.textRef.querySelectorAll('.code-block-header__copy')
|
this.removeBtnClickEvent('.code-block-header__copy')
|
||||||
copyBtn.forEach((btn) => {
|
this.addBtnClickEvents('.code-block-header__insert')
|
||||||
btn.removeEventListener('click', () => {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,6 +140,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.markdown-body {
|
.markdown-body {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
max-width: 300px;;
|
||||||
|
|
||||||
&::v-deep p {
|
&::v-deep p {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
@@ -115,26 +158,46 @@ export default {
|
|||||||
|
|
||||||
&::v-deep .code-block-wrapper {
|
&::v-deep .code-block-wrapper {
|
||||||
background: #1F2329;
|
background: #1F2329;
|
||||||
padding: 2px 6px;
|
padding: 0;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.code-block-body {
|
.code-block-body {
|
||||||
padding: 5px 10px 0;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
|
|
||||||
.code-block-header {
|
.code-block-header {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #353946;
|
background: #353946;
|
||||||
color: #c2d1e1;
|
color: #c2d1e1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.code-block-header__copy {
|
.code-block-header__actions {
|
||||||
float: right;
|
display: flex;
|
||||||
cursor: pointer;
|
gap: 8px;
|
||||||
|
|
||||||
&:hover {
|
.code-block-header__copy {
|
||||||
color: #6e747b;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #6e747b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-header__insert {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #6e747b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +241,7 @@ export default {
|
|||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChatMessage v-for="(item, index) in activeChat.chats" :key="index" :item="item" />
|
<ChatMessage v-for="(item, index) in activeChat.chats" :key="index" :item="item" :is-terminal="isTerminal" @insert-code="insertCode" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
@click="onStopHandle"
|
@click="onStopHandle"
|
||||||
>{{ $tc('Stop') }}</el-button>
|
>{{ $tc('Stop') }}</el-button>
|
||||||
<ChatInput ref="chatInput" :expanded="expanded" @send="onSendHandle" @select-prompt="onSelectPromptHandle" />
|
<ChatInput ref="chatInput" :expanded="expanded" :has-prompt="!isTerminal" @send="onSendHandle" @select-prompt="onSelectPromptHandle" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -68,7 +68,10 @@ export default {
|
|||||||
prompt: '',
|
prompt: '',
|
||||||
conversationId: '',
|
conversationId: '',
|
||||||
showIntroduction: false,
|
showIntroduction: false,
|
||||||
introduction: []
|
introduction: [],
|
||||||
|
terminalContext: null,
|
||||||
|
isTerminal: false,
|
||||||
|
sessionChat: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -100,6 +103,9 @@ export default {
|
|||||||
this.showIntroduction = true
|
this.showIntroduction = true
|
||||||
this.conversationId = ''
|
this.conversationId = ''
|
||||||
this.$refs.chatInput.select.value = ''
|
this.$refs.chatInput.select.value = ''
|
||||||
|
if (this.terminalContext) {
|
||||||
|
this.prompt = this.terminalContext.content || ''
|
||||||
|
}
|
||||||
const chat = {
|
const chat = {
|
||||||
message: {
|
message: {
|
||||||
content: this.$t('ChatHello'),
|
content: this.$t('ChatHello'),
|
||||||
@@ -150,6 +156,32 @@ export default {
|
|||||||
addMessageToActiveChat(data)
|
addMessageToActiveChat(data)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
},
|
},
|
||||||
|
onTerminalContext(terminalContext) {
|
||||||
|
const originSessionId = this.terminalContext?.sessionId
|
||||||
|
const newSessionId = terminalContext.sessionId || ''
|
||||||
|
if (originSessionId) {
|
||||||
|
this.saveSessionChat(originSessionId)
|
||||||
|
}
|
||||||
|
this.terminalContext = terminalContext
|
||||||
|
this.isTerminal = true
|
||||||
|
this.prompt = terminalContext.content || ''
|
||||||
|
if (originSessionId !== newSessionId) {
|
||||||
|
if (this.sessionChat[newSessionId]) {
|
||||||
|
clearChats()
|
||||||
|
for (const chat of this.sessionChat[newSessionId]) {
|
||||||
|
addChatMessageById(chat)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.onNewChat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveSessionChat(sessionId) {
|
||||||
|
if (this.terminalContext) {
|
||||||
|
this.sessionChat[sessionId] = JSON.parse(JSON.stringify(this.activeChat.chats))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onSendHandle(value) {
|
onSendHandle(value) {
|
||||||
this.showIntroduction = false
|
this.showIntroduction = false
|
||||||
this.socket = ws || {}
|
this.socket = ws || {}
|
||||||
@@ -204,6 +236,15 @@ export default {
|
|||||||
sendIntroduction(item) {
|
sendIntroduction(item) {
|
||||||
this.showIntroduction = false
|
this.showIntroduction = false
|
||||||
this.onSendHandle(item.content)
|
this.onSendHandle(item.content)
|
||||||
|
},
|
||||||
|
insertCode(code) {
|
||||||
|
this.sendPostMessage({
|
||||||
|
name: 'INSERT_TERMINAL_CODE',
|
||||||
|
data: code.replace(/^[\s\r\n]+|[\s\r\n]+$/g, '')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendPostMessage(data) {
|
||||||
|
window.parent.postMessage(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ import Chat from './components/ChitChat/index.vue'
|
|||||||
import { getInputFocus } from './useChat.js'
|
import { getInputFocus } from './useChat.js'
|
||||||
import { ws } from '@/utils/socket'
|
import { ws } from '@/utils/socket'
|
||||||
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
||||||
|
import { ObjectLocalStorage } from '@/utils/common'
|
||||||
|
|
||||||
|
const aiPannelLocalStorage = new ObjectLocalStorage('ai_panel_settings')
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DrawerPanel,
|
DrawerPanel,
|
||||||
@@ -76,12 +78,15 @@ export default {
|
|||||||
robotUrl: require('@/assets/img/robot-assistant.png'),
|
robotUrl: require('@/assets/img/robot-assistant.png'),
|
||||||
height: '400px',
|
height: '400px',
|
||||||
expanded: false,
|
expanded: false,
|
||||||
clientOffset: {}
|
clientOffset: {},
|
||||||
|
currentTerminalContent: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
const expanded = aiPannelLocalStorage.get('expanded')
|
||||||
|
this.updateExpandedState(expanded)
|
||||||
this.handlePostMessage()
|
this.handlePostMessage()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -89,6 +94,17 @@ export default {
|
|||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.data === 'show-chat-panel') {
|
if (event.data === 'show-chat-panel') {
|
||||||
this.$refs.drawer.show = true
|
this.$refs.drawer.show = true
|
||||||
|
this.initWebSocket()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const msg = event.data
|
||||||
|
switch (msg.name) {
|
||||||
|
case 'current_terminal_content':
|
||||||
|
// {content: '...', terminalId: '',sessionId: '',viewId: '',viewName: ''}
|
||||||
|
this.$log.debug('current_terminal_content', msg)
|
||||||
|
this.currentTerminalContent = msg.data
|
||||||
|
this.$refs.component?.onTerminalContext(msg.data)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -96,6 +112,11 @@ export default {
|
|||||||
this.$refs.drawer.handleHeaderMoveDown(event)
|
this.$refs.drawer.handleHeaderMoveDown(event)
|
||||||
},
|
},
|
||||||
handleMouseMoveUp(event) {
|
handleMouseMoveUp(event) {
|
||||||
|
// Prevent the new chat button from triggering the header move up
|
||||||
|
const newButton = event.target.closest('.new')
|
||||||
|
if (newButton) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.$refs.drawer.handleHeaderMoveUp(event)
|
this.$refs.drawer.handleHeaderMoveUp(event)
|
||||||
},
|
},
|
||||||
initWebSocket() {
|
initWebSocket() {
|
||||||
@@ -107,12 +128,20 @@ export default {
|
|||||||
this.$refs.drawer.show = false
|
this.$refs.drawer.show = false
|
||||||
},
|
},
|
||||||
expandFull() {
|
expandFull() {
|
||||||
this.height = '100%'
|
this.updateExpandedState(true)
|
||||||
this.expanded = true
|
this.save_pannel_settings()
|
||||||
},
|
},
|
||||||
compress() {
|
compress() {
|
||||||
this.height = '400px'
|
this.updateExpandedState(false)
|
||||||
this.expanded = false
|
this.save_pannel_settings()
|
||||||
|
},
|
||||||
|
save_pannel_settings() {
|
||||||
|
aiPannelLocalStorage.set('expanded', this.expanded)
|
||||||
|
console.log('AI panel settings saved:', this.expanded)
|
||||||
|
},
|
||||||
|
updateExpandedState(expanded) {
|
||||||
|
this.expanded = expanded
|
||||||
|
this.height = expanded ? '100%' : '400px'
|
||||||
},
|
},
|
||||||
onNewChat() {
|
onNewChat() {
|
||||||
this.active = 'chat'
|
this.active = 'chat'
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
:src="faceCaptureUrl"
|
:src="faceCaptureUrl"
|
||||||
allow="camera"
|
allow="camera"
|
||||||
sandbox="allow-scripts allow-same-origin"
|
sandbox="allow-scripts allow-same-origin"
|
||||||
style="width: 100%; height: 800px;border: none;"
|
style="width: 100%; height: 600px;border: none;"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="subTypeSelected === 'face'"
|
v-if="subTypeSelected === 'face'"
|
||||||
:disabled="isFaceCaptureVisible"
|
v-show="!isFaceCaptureVisible"
|
||||||
class="confirm-btn"
|
class="confirm-btn"
|
||||||
size="mini"
|
size="mini"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
162
src/components/Apps/UserSelect/dialog.vue
Normal file
162
src/components/Apps/UserSelect/dialog.vue
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:title="$tc('Users')"
|
||||||
|
custom-class="user-select-dialog"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
v-on="$listeners"
|
||||||
|
>
|
||||||
|
<ListTable
|
||||||
|
ref="ListPage"
|
||||||
|
:header-actions="headerActions"
|
||||||
|
:sync-select-to-url="false"
|
||||||
|
:table-config="tableConfig"
|
||||||
|
:url="baseUrl"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@loaded="handleTableLoaded"
|
||||||
|
v-on="$listeners"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dialog from '@/components/Dialog'
|
||||||
|
import ListTable from '@/components/Table/ListTable'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
componentName: 'AssetSelectDialog',
|
||||||
|
components: { Dialog, ListTable },
|
||||||
|
props: {
|
||||||
|
baseUrl: {
|
||||||
|
type: String,
|
||||||
|
default: '/api/v1/users/users/?fields_size=small'
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
canSelect: {
|
||||||
|
type: Function,
|
||||||
|
default(row, index) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: [Boolean, Function],
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const vm = this
|
||||||
|
return {
|
||||||
|
isLoaded: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
rowSelected: _.cloneDeep(this.value) || [],
|
||||||
|
rowsAdd: [],
|
||||||
|
tableConfig: {
|
||||||
|
url: this.baseUrl,
|
||||||
|
canSelect: this.canSelect,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
prop: 'name',
|
||||||
|
label: this.$t('Name'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'username',
|
||||||
|
label: this.$t('Username'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'email',
|
||||||
|
label: this.$t('Email'),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'mfa_level.label',
|
||||||
|
label: this.$t('MfaLevel'),
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(row) {
|
||||||
|
return row.mfa_level.label
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'source.label',
|
||||||
|
label: this.$t('Source'),
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(row) {
|
||||||
|
return row.source.label
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'org_roles.display_name',
|
||||||
|
label: this.$t('OrgRoles'),
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(row) {
|
||||||
|
return row.org_roles.map(role => role.display_name).join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
columnsMeta: {
|
||||||
|
actions: {
|
||||||
|
has: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listeners: {
|
||||||
|
'toggle-row-selection': (isSelected, row) => {
|
||||||
|
if (isSelected) {
|
||||||
|
vm.addRowToSelect(row)
|
||||||
|
} else {
|
||||||
|
vm.removeRowFromSelect(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
theRowDefaultIsSelected: (row) => {
|
||||||
|
return this.value.indexOf(row.id) > -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headerActions: {
|
||||||
|
hasLeftActions: false,
|
||||||
|
hasImport: false,
|
||||||
|
hasExport: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTableLoaded() {
|
||||||
|
this.isLoaded = true
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$emit('confirm', this.rowSelected, this.rowsAdd)
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.$emit('cancel')
|
||||||
|
},
|
||||||
|
addRowToSelect(row) {
|
||||||
|
const selectValueIndex = this.rowSelected.indexOf(row.id)
|
||||||
|
if (selectValueIndex === -1) {
|
||||||
|
this.rowSelected.push(row.id)
|
||||||
|
this.rowsAdd.push(row)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeRowFromSelect(row) {
|
||||||
|
const selectValueIndex = this.rowSelected.indexOf(row.id)
|
||||||
|
if (selectValueIndex > -1) {
|
||||||
|
this.rowSelected.splice(selectValueIndex, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page ::v-deep .page-heading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-select-dialog ::v-deep .el-icon-circle-check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
130
src/components/Apps/UserSelect/index.vue
Normal file
130
src/components/Apps/UserSelect/index.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<template>
|
||||||
|
<div class="user-select-formatter">
|
||||||
|
<Select2
|
||||||
|
ref="select2"
|
||||||
|
v-model="select2Config.value"
|
||||||
|
v-bind="select2Config"
|
||||||
|
@input="onInputChange"
|
||||||
|
v-on="$listeners"
|
||||||
|
@focus.stop="handleFocus"
|
||||||
|
/>
|
||||||
|
<UserSelectDialog
|
||||||
|
v-if="dialogVisible"
|
||||||
|
ref="dialog"
|
||||||
|
:base-url="baseUrl"
|
||||||
|
:value="value"
|
||||||
|
:visible.sync="dialogVisible"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
v-on="$listeners"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Select2 from '@/components/Form/FormFields/Select2.vue'
|
||||||
|
import UserSelectDialog from './dialog.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
componentName: 'UserSelect',
|
||||||
|
components: { UserSelectDialog, Select2 },
|
||||||
|
props: {
|
||||||
|
baseUrl: {
|
||||||
|
type: String,
|
||||||
|
default: '/api/v1/users/users/?fields_size=small'
|
||||||
|
},
|
||||||
|
defaultPageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: [Boolean, Function],
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const iValue = []
|
||||||
|
for (const item of this.value) {
|
||||||
|
if (typeof item === 'object') {
|
||||||
|
iValue.push(item.id)
|
||||||
|
} else {
|
||||||
|
iValue.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
dialogVisible: false,
|
||||||
|
initialValue: _.cloneDeep(iValue),
|
||||||
|
select2Config: {
|
||||||
|
disabled: this.disabled,
|
||||||
|
value: iValue,
|
||||||
|
multiple: true,
|
||||||
|
clearable: true,
|
||||||
|
defaultPageSize: this.defaultPageSize,
|
||||||
|
ajax: {
|
||||||
|
url: this.baseUrl,
|
||||||
|
transformOption: (item) => {
|
||||||
|
return { label: item.name + '(' + item.username + ')', value: item.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleFocus() {
|
||||||
|
this.$refs.select2.selectRef.blur()
|
||||||
|
this.dialogVisible = true
|
||||||
|
},
|
||||||
|
handleConfirm(valueSelected, rowsAdd) {
|
||||||
|
if (valueSelected === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$refs.select2.iValue = valueSelected
|
||||||
|
this.addRowsToSelect(rowsAdd)
|
||||||
|
this.onInputChange(valueSelected)
|
||||||
|
this.dialogVisible = false
|
||||||
|
},
|
||||||
|
handleCancel() {
|
||||||
|
this.dialogVisible = false
|
||||||
|
},
|
||||||
|
onInputChange(val) {
|
||||||
|
this.$emit('change', val)
|
||||||
|
},
|
||||||
|
addToSelect(options, row) {
|
||||||
|
const selectOptionsHas = options.find(item => item.value === row.id)
|
||||||
|
// 如果select2的options中没有,那么可能无法显示正常的值
|
||||||
|
if (selectOptionsHas === undefined) {
|
||||||
|
const option = {
|
||||||
|
label: `${row.name}(${row.username})`,
|
||||||
|
value: row.id
|
||||||
|
}
|
||||||
|
options.push(option)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addRowsToSelect(rows) {
|
||||||
|
const outSelectOptions = this.$refs.select2.options
|
||||||
|
for (const row of rows) {
|
||||||
|
this.addToSelect(outSelectOptions, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page ::v-deep .page-heading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page ::v-deep .treebox {
|
||||||
|
height: inherit !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -39,6 +39,9 @@ export default {
|
|||||||
['', ['name', 'var_name', 'type', 'text_default_value', 'select_default_value', 'extra_args', 'tips', 'required']]
|
['', ['name', 'var_name', 'type', 'text_default_value', 'select_default_value', 'extra_args', 'tips', 'required']]
|
||||||
],
|
],
|
||||||
fieldsMeta: {
|
fieldsMeta: {
|
||||||
|
var_name: {
|
||||||
|
helpTextAsTip: false
|
||||||
|
},
|
||||||
text_default_value: {
|
text_default_value: {
|
||||||
label: this.$t('DefaultValue'),
|
label: this.$t('DefaultValue'),
|
||||||
hidden: (formValue) => {
|
hidden: (formValue) => {
|
||||||
|
|||||||
@@ -140,14 +140,11 @@ export default {
|
|||||||
this._cleanFormValue(this.iForm, this.remoteMeta)
|
this._cleanFormValue(this.iForm, this.remoteMeta)
|
||||||
},
|
},
|
||||||
setFieldError(name, error) {
|
setFieldError(name, error) {
|
||||||
|
error = error.replace(/[。.]+$/, '')
|
||||||
const field = this.totalFields.find((v) => v.prop === name)
|
const field = this.totalFields.find((v) => v.prop === name)
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (field.attrs.error === error) {
|
|
||||||
error += '.'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
field.el.errors = error
|
field.el.errors = error
|
||||||
field.attrs.error = error
|
field.attrs.error = error
|
||||||
|
|||||||
@@ -24,16 +24,17 @@ const formatterArgsDefault = {
|
|||||||
true: 'text-primary',
|
true: 'text-primary',
|
||||||
false: 'text-danger'
|
false: 'text-danger'
|
||||||
},
|
},
|
||||||
textChoices: {
|
|
||||||
true: i18n.t('Yes'),
|
|
||||||
false: i18n.t('No')
|
|
||||||
},
|
|
||||||
getKey({ row, cellValue }) {
|
getKey({ row, cellValue }) {
|
||||||
return (cellValue && typeof cellValue === 'object') ? cellValue.value : cellValue
|
return (cellValue && typeof cellValue === 'object') ? cellValue.value : cellValue
|
||||||
},
|
},
|
||||||
getText({ row, cellValue }) {
|
getText({ row, cellValue }) {
|
||||||
const key = this.getKey({ row, cellValue })
|
const key = this.getKey({ row, cellValue })
|
||||||
return (cellValue && typeof cellValue === 'object') ? cellValue.label : this.textChoices[key] || cellValue
|
if (cellValue && typeof cellValue === 'object') {
|
||||||
|
return cellValue.label
|
||||||
|
}
|
||||||
|
if (key === true || key === 'true') return i18n.t('Yes')
|
||||||
|
if (key === false || key === 'false') return i18n.t('No')
|
||||||
|
return cellValue
|
||||||
},
|
},
|
||||||
getIcon({ row, cellValue }) {
|
getIcon({ row, cellValue }) {
|
||||||
const key = this.getKey({ row, cellValue })
|
const key = this.getKey({ row, cellValue })
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import { message } from '@/utils/message'
|
|||||||
import xss from '@/utils/xss'
|
import xss from '@/utils/xss'
|
||||||
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
|
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
|
||||||
import VSanitize from 'v-sanitize'
|
import VSanitize from 'v-sanitize'
|
||||||
|
import moment from 'moment'
|
||||||
|
moment.locale('zh-cn')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you don't want to use mock-server
|
* If you don't want to use mock-server
|
||||||
* you want to use MockJs for mock api
|
* you want to use MockJs for mock api
|
||||||
@@ -50,11 +53,7 @@ Vue.config.productionTip = false
|
|||||||
Vue.use(VueCookie)
|
Vue.use(VueCookie)
|
||||||
window.$cookie = VueCookie
|
window.$cookie = VueCookie
|
||||||
|
|
||||||
const moment = require('moment')
|
Vue.prototype.$moment = moment
|
||||||
require('moment/locale/zh-cn')
|
|
||||||
Vue.use(require('vue-moment'), {
|
|
||||||
moment
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.use(VueLogger, loggerOptions)
|
Vue.use(VueLogger, loggerOptions)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ for (let i = 0; i < chars.length; i++) {
|
|||||||
|
|
||||||
const encode = function(arraybuffer) {
|
const encode = function(arraybuffer) {
|
||||||
const bytes = new Uint8Array(arraybuffer)
|
const bytes = new Uint8Array(arraybuffer)
|
||||||
let i; const len = bytes.length; let base64url = ''
|
let i
|
||||||
|
const len = bytes.length
|
||||||
|
let base64url = ''
|
||||||
|
|
||||||
for (i = 0; i < len; i += 3) {
|
for (i = 0; i < len; i += 3) {
|
||||||
base64url += chars[bytes[i] >> 2]
|
base64url += chars[bytes[i] >> 2]
|
||||||
@@ -17,7 +19,7 @@ const encode = function(arraybuffer) {
|
|||||||
base64url += chars[bytes[i + 2] & 63]
|
base64url += chars[bytes[i + 2] & 63]
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((len % 3) === 2) {
|
if (len % 3 === 2) {
|
||||||
base64url = base64url.substring(0, base64url.length - 1)
|
base64url = base64url.substring(0, base64url.length - 1)
|
||||||
} else if (len % 3 === 1) {
|
} else if (len % 3 === 1) {
|
||||||
base64url = base64url.substring(0, base64url.length - 2)
|
base64url = base64url.substring(0, base64url.length - 2)
|
||||||
@@ -28,8 +30,13 @@ const encode = function(arraybuffer) {
|
|||||||
|
|
||||||
const decode = function(base64string) {
|
const decode = function(base64string) {
|
||||||
const bufferLength = base64string.length * 0.75
|
const bufferLength = base64string.length * 0.75
|
||||||
const len = base64string.length; let i; let p = 0
|
const len = base64string.length
|
||||||
let encoded1; let encoded2; let encoded3; let encoded4
|
let i
|
||||||
|
let p = 0
|
||||||
|
let encoded1
|
||||||
|
let encoded2
|
||||||
|
let encoded3
|
||||||
|
let encoded4
|
||||||
|
|
||||||
const bytes = new Uint8Array(bufferLength)
|
const bytes = new Uint8Array(bufferLength)
|
||||||
|
|
||||||
@@ -47,15 +54,16 @@ const decode = function(base64string) {
|
|||||||
return bytes.buffer
|
return bytes.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeyCredentialToJSON = (pubKeyCred) => {
|
const publicKeyCredentialToJSON = pubKeyCred => {
|
||||||
if (pubKeyCred instanceof Array) {
|
if (pubKeyCred instanceof Array) {
|
||||||
const arr = []
|
const arr = []
|
||||||
for (const i of pubKeyCred) { arr.push(publicKeyCredentialToJSON(i)) }
|
for (const i of pubKeyCred) {
|
||||||
|
arr.push(publicKeyCredentialToJSON(i))
|
||||||
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pubKeyCred instanceof ArrayBuffer) {
|
if (pubKeyCred instanceof ArrayBuffer || pubKeyCred instanceof Uint8Array) {
|
||||||
return encode(pubKeyCred)
|
return encode(pubKeyCred)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +81,7 @@ const publicKeyCredentialToJSON = (pubKeyCred) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'decode': decode,
|
decode: decode,
|
||||||
'encode': encode,
|
encode: encode,
|
||||||
'publicKeyCredentialToJSON': publicKeyCredentialToJSON
|
publicKeyCredentialToJSON: publicKeyCredentialToJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default {
|
|||||||
executed_amount: {
|
executed_amount: {
|
||||||
formatter: (row) => {
|
formatter: (row) => {
|
||||||
const can = vm.$hasPerm('accounts.view_changesecretexecution')
|
const can = vm.$hasPerm('accounts.view_changesecretexecution')
|
||||||
return <el-link onClick={ () => this.handleExecAmount(row) } disabled={ !can }>{ row.executed_amount }</el-link>
|
return <el-link onClick={() => this.handleExecAmount(row)} disabled={!can}>{row.executed_amount}</el-link>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@@ -101,7 +101,23 @@ export default {
|
|||||||
hasRefresh: true,
|
hasRefresh: true,
|
||||||
hasExport: false,
|
hasExport: false,
|
||||||
hasImport: false,
|
hasImport: false,
|
||||||
createRoute: 'AccountChangeSecretCreate'
|
createRoute: 'AccountChangeSecretCreate',
|
||||||
|
extraMoreActions: [
|
||||||
|
{
|
||||||
|
name: 'BatchDisable',
|
||||||
|
title: this.$t('DisableSelected'),
|
||||||
|
icon: 'fa fa-ban',
|
||||||
|
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_changesecretautomation'),
|
||||||
|
callback: ({ selectedRows, reloadTable }) => this.bulkDisableCallback(selectedRows, reloadTable)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BatchActivate',
|
||||||
|
title: this.$t('ActivateSelected'),
|
||||||
|
icon: 'fa fa-check-circle-o',
|
||||||
|
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_changesecretautomation'),
|
||||||
|
callback: ({ selectedRows, reloadTable }) => this.bulkActivateCallback(selectedRows, reloadTable)
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -114,6 +130,32 @@ export default {
|
|||||||
automation_id: row.id
|
automation_id: row.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
bulkDisableCallback(selectedRows, reloadTable) {
|
||||||
|
const url = '/api/v1/accounts/change-secret-automations/'
|
||||||
|
const data = selectedRows.map(row => {
|
||||||
|
return { id: row.id, is_active: false }
|
||||||
|
})
|
||||||
|
if (data.length === 0) return
|
||||||
|
this.$axios.patch(url, data).then(() => {
|
||||||
|
reloadTable()
|
||||||
|
this.$message.success(this.$t('DisableSuccessMsg'))
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error(this.$t('UpdateErrorMsg') + ' ' + error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
bulkActivateCallback(selectedRows, reloadTable) {
|
||||||
|
const url = '/api/v1/accounts/change-secret-automations/'
|
||||||
|
const data = selectedRows.map(row => {
|
||||||
|
return { id: row.id, is_active: true }
|
||||||
|
})
|
||||||
|
if (data.length === 0) return
|
||||||
|
this.$axios.patch(url, data).then(() => {
|
||||||
|
reloadTable()
|
||||||
|
this.$message.success(this.$t('ActivateSuccessMsg'))
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error(this.$t('UpdateErrorMsg') + ' ' + error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ export default {
|
|||||||
BaseExecutionList
|
BaseExecutionList
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const params = new URLSearchParams(this.$route.params).toString()
|
|
||||||
return {
|
return {
|
||||||
url: `/api/v1/accounts/change-secret-executions/?${params}`,
|
url: `/api/v1/accounts/change-secret-executions/`,
|
||||||
detailRoute: 'AccountChangeSecretExecutionDetail',
|
detailRoute: 'AccountChangeSecretExecutionDetail',
|
||||||
automationRoute: 'AccountChangeSecretDetail',
|
automationRoute: 'AccountChangeSecretDetail',
|
||||||
resource: 'changesecretexecution',
|
resource: 'changesecretexecution',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
:create-drawer="createDrawer"
|
:create-drawer="createDrawer"
|
||||||
:detail-drawer="detailDrawer"
|
:detail-drawer="detailDrawer"
|
||||||
:header-actions="headerActions"
|
:header-actions="headerActions"
|
||||||
:resource="$tc('AccountDiscoverTask')"
|
:resource="$tc('DiscoverAccountTask')"
|
||||||
:table-config="tableConfig"
|
:table-config="tableConfig"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -104,7 +104,23 @@ export default {
|
|||||||
hasRefresh: true,
|
hasRefresh: true,
|
||||||
hasExport: false,
|
hasExport: false,
|
||||||
hasImport: false,
|
hasImport: false,
|
||||||
createRoute: 'AccountPushCreate'
|
createRoute: 'AccountPushCreate',
|
||||||
|
extraMoreActions: [
|
||||||
|
{
|
||||||
|
name: 'BatchDisable',
|
||||||
|
title: this.$t('DisableSelected'),
|
||||||
|
icon: 'fa fa-ban',
|
||||||
|
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_pushaccountautomation'),
|
||||||
|
callback: ({ selectedRows, reloadTable }) => this.bulkDisableCallback(selectedRows, reloadTable)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BatchActivate',
|
||||||
|
title: this.$t('ActivateSelected'),
|
||||||
|
icon: 'fa fa-check-circle-o',
|
||||||
|
can: ({ selectedRows }) => selectedRows.length > 0 && this.$hasPerm('accounts.change_pushaccountautomation'),
|
||||||
|
callback: ({ selectedRows, reloadTable }) => this.bulkActivateCallback(selectedRows, reloadTable)
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -117,6 +133,32 @@ export default {
|
|||||||
automation_id: row.id
|
automation_id: row.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
bulkDisableCallback(selectedRows, reloadTable) {
|
||||||
|
const url = '/api/v1/accounts/push-account-automations/'
|
||||||
|
const data = selectedRows.map(row => {
|
||||||
|
return { id: row.id, is_active: false }
|
||||||
|
})
|
||||||
|
if (data.length === 0) return
|
||||||
|
this.$axios.patch(url, data).then(() => {
|
||||||
|
reloadTable()
|
||||||
|
this.$message.success(this.$t('DisableSuccessMsg'))
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error(this.$t('UpdateErrorMsg') + ' ' + error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
bulkActivateCallback(selectedRows, reloadTable) {
|
||||||
|
const url = '/api/v1/accounts/push-account-automations/'
|
||||||
|
const data = selectedRows.map(row => {
|
||||||
|
return { id: row.id, is_active: true }
|
||||||
|
})
|
||||||
|
if (data.length === 0) return
|
||||||
|
this.$axios.patch(url, data).then(() => {
|
||||||
|
reloadTable()
|
||||||
|
this.$message.success(this.$t('DisableSuccessMsg'))
|
||||||
|
}).catch(error => {
|
||||||
|
this.$message.error(this.$t('UpdateErrorMsg') + ' ' + error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:detail-drawer="detailDrawer"
|
:detail-drawer="detailDrawer"
|
||||||
:header-actions="headerActions"
|
:header-actions="headerActions"
|
||||||
:table-config="tableConfig"
|
:table-config="tableConfig"
|
||||||
|
:resource="$t('AccountTemplate')"
|
||||||
/>
|
/>
|
||||||
<ViewSecret
|
<ViewSecret
|
||||||
v-if="showViewSecretDialog"
|
v-if="showViewSecretDialog"
|
||||||
|
|||||||
@@ -155,11 +155,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const automation_id = this.$route.query.automation_id
|
|
||||||
if (automation_id !== undefined) {
|
|
||||||
this.tableConfig.url = `${this.tableConfig.url}?automation_id=${automation_id}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultExtraActions = this.tableConfig.columnsMeta.actions.formatterArgs.extraActions
|
const defaultExtraActions = this.tableConfig.columnsMeta.actions.formatterArgs.extraActions
|
||||||
|
|
||||||
if (this.customActions) {
|
if (this.customActions) {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<GenericListTable ref="listTable" :header-actions="headerActions" :table-config="tableConfig" />
|
<GenericListTable
|
||||||
|
ref="listTable"
|
||||||
|
:header-actions="headerActions"
|
||||||
|
:table-config="tableConfig"
|
||||||
|
:resource="$t('DetectTasks')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
name: 'Template'
|
name: 'Template'
|
||||||
},
|
},
|
||||||
cleanFormValue(value) {
|
cleanFormValue(value) {
|
||||||
const isClone = this?.$route?.query.clone_from !== undefined
|
const isClone = this?.action === 'clone'
|
||||||
if (isClone) {
|
if (isClone) {
|
||||||
value?.variable.map((item) => {
|
value?.variable.map((item) => {
|
||||||
delete item.id
|
delete item.id
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
name: 'Template'
|
name: 'Template'
|
||||||
},
|
},
|
||||||
cleanFormValue(value) {
|
cleanFormValue(value) {
|
||||||
const isClone = this?.$route?.query.clone_from !== undefined
|
const isClone = this?.action === 'clone'
|
||||||
if (isClone) {
|
if (isClone) {
|
||||||
value?.variable.map((item) => {
|
value?.variable.map((item) => {
|
||||||
delete item.id
|
delete item.id
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||||
|
import UserSelect from '@/components/Apps/UserSelect'
|
||||||
import AssetSelect from '@/components/Apps/AssetSelect'
|
import AssetSelect from '@/components/Apps/AssetSelect'
|
||||||
import AccountFormatter from './components/AccountFormatter'
|
import AccountFormatter from './components/AccountFormatter'
|
||||||
import { AllAccount } from '../const'
|
import { AllAccount } from '../const'
|
||||||
@@ -49,14 +50,15 @@ export default {
|
|||||||
createSuccessNextRoute: { name: 'AssetPermissionDetail' },
|
createSuccessNextRoute: { name: 'AssetPermissionDetail' },
|
||||||
fieldsMeta: {
|
fieldsMeta: {
|
||||||
users: {
|
users: {
|
||||||
|
type: 'userSelect',
|
||||||
|
component: UserSelect,
|
||||||
|
rules: [{
|
||||||
|
required: false
|
||||||
|
}],
|
||||||
el: {
|
el: {
|
||||||
value: [],
|
value: [],
|
||||||
ajax: {
|
defaultPageSize: 300,
|
||||||
url: '/api/v1/users/users/?fields_size=mini',
|
baseUrl: '/api/v1/users/users/?fields_size=small'
|
||||||
transformOption: (item) => {
|
|
||||||
return { label: item.name + '(' + item.username + ')', value: item.id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
user_groups: {
|
user_groups: {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export default {
|
|||||||
name: 'replay',
|
name: 'replay',
|
||||||
title: this.$t('Replay'),
|
title: this.$t('Replay'),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
can: ({ row }) => vm.hasPerms(row, 'view'),
|
// TODO 当前版本 magnus 代理的 mongodb 协议的 session 不支持 replay
|
||||||
|
can: ({ row }) => vm.hasPerms(row, 'view') && !(row.protocol === 'mongodb' && row.terminal.type === 'magnus'),
|
||||||
callback: function({ row, tableData }) {
|
callback: function({ row, tableData }) {
|
||||||
// 跳转到luna页面
|
// 跳转到luna页面
|
||||||
const replayUrl = '/luna/replay/' + row.id
|
const replayUrl = '/luna/replay/' + row.id
|
||||||
@@ -36,7 +37,7 @@ export default {
|
|||||||
name: 'download',
|
name: 'download',
|
||||||
title: this.$t('Download'),
|
title: this.$t('Download'),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
can: ({ row }) => vm.hasPerms(row, 'download'),
|
can: ({ row }) => vm.hasPerms(row, 'download') && !(row.protocol === 'mongodb' && row.terminal.type === 'magnus'),
|
||||||
callback: function({ row, tableData }) {
|
callback: function({ row, tableData }) {
|
||||||
// 跳转下载页面
|
// 跳转下载页面
|
||||||
download(`/api/v1/terminal/sessions/${row.id}/replay/download/`)
|
download(`/api/v1/terminal/sessions/${row.id}/replay/download/`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:before-close="handleClose"
|
:before-close="handleClose"
|
||||||
:loading-status="!isFinished"
|
:disabled-status="!isFinished"
|
||||||
:show-cancel="false"
|
:show-cancel="false"
|
||||||
:title="$tc('OfflineUpload')"
|
:title="$tc('OfflineUpload')"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@@ -104,6 +104,8 @@ export default {
|
|||||||
const error = err.response.data
|
const error = err.response.data
|
||||||
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
||||||
this.$message.error(msg)
|
this.$message.error(msg)
|
||||||
|
}).finally(() => {
|
||||||
|
this.$refs.upload.clearFiles()
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
ref="table"
|
ref="table"
|
||||||
class="applet-host"
|
class="applet-host"
|
||||||
:create-drawer="createDrawer"
|
:create-drawer="createDrawer"
|
||||||
|
:resource="$t('AppletHosts')"
|
||||||
v-bind="$data"
|
v-bind="$data"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:before-close="handleClose"
|
:before-close="handleClose"
|
||||||
:loading-status="!isFinished"
|
:disabled-status="!isFinished"
|
||||||
:show-cancel="false"
|
:show-cancel="false"
|
||||||
:title="$tc('OfflineUpload')"
|
:title="$tc('OfflineUpload')"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@@ -102,11 +102,9 @@ export default {
|
|||||||
const error = err.response.data
|
const error = err.response.data
|
||||||
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
||||||
this.$message.error(msg)
|
this.$message.error(msg)
|
||||||
})
|
}).finally(() => {
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$refs.upload.clearFiles()
|
this.$refs.upload.clearFiles()
|
||||||
}, 400)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20" class="task-detail">
|
||||||
<el-col :md="20" :sm="24">
|
<el-col :md="20" :sm="24">
|
||||||
<DetailCard :items="detailCardItems" :title="cardTitle" />
|
<DetailCard :items="detailCardItems" :title="cardTitle" />
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -55,7 +55,8 @@ export default {
|
|||||||
methods: {}
|
methods: {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.task-detail /deep/ .item-value span {
|
||||||
|
white-space: normal !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default {
|
|||||||
'',
|
'',
|
||||||
[
|
[
|
||||||
'mysql_port', 'mariadb_port', 'postgresql_port',
|
'mysql_port', 'mariadb_port', 'postgresql_port',
|
||||||
'redis_port', 'sqlserver_port', 'oracle_port'
|
'redis_port', 'sqlserver_port', 'oracle_port',
|
||||||
|
'mongodb_port'
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[this.$t('Other'), ['is_active', 'comment']]
|
[this.$t('Other'), ['is_active', 'comment']]
|
||||||
@@ -39,9 +40,6 @@ export default {
|
|||||||
host: {
|
host: {
|
||||||
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
||||||
},
|
},
|
||||||
oracle_port: {
|
|
||||||
disabled: true
|
|
||||||
},
|
|
||||||
is_active: {
|
is_active: {
|
||||||
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:header-actions="headerActions"
|
:header-actions="headerActions"
|
||||||
:table-config="tableConfig"
|
:table-config="tableConfig"
|
||||||
:create-drawer="createDrawer"
|
:create-drawer="createDrawer"
|
||||||
|
:resource="$t('Endpoint')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:header-actions="headerActions"
|
:header-actions="headerActions"
|
||||||
:table-config="tableConfig"
|
:table-config="tableConfig"
|
||||||
:create-drawer="createDrawer"
|
:create-drawer="createDrawer"
|
||||||
|
:resource="$t('EndpointRules')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<GenericDetailPage :active-menu.sync="config.activeMenu" :object.sync="ticket" v-bind="config" v-on="$listeners">
|
<GenericDetailPage :title="ticket.title" :active-menu.sync="config.activeMenu" :object.sync="ticket" v-bind="config" v-on="$listeners">
|
||||||
<component :is="config.activeMenu" :object="ticket" />
|
<component :is="config.activeMenu" :object="ticket" />
|
||||||
</GenericDetailPage>
|
</GenericDetailPage>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
29
yarn.lock
29
yarn.lock
@@ -8914,7 +8914,7 @@ moment-parseformat@^4.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/moment-parseformat/-/moment-parseformat-4.0.0.tgz"
|
resolved "https://registry.npmmirror.com/moment-parseformat/-/moment-parseformat-4.0.0.tgz"
|
||||||
integrity sha512-0V4ICKnI1npglqrMSDK2y8WxOdN79DkMoIexzY3P+jr2wNfbB4J81BgjFfHsj18wBsV7FdKCWyCHcezzH0xlyg==
|
integrity sha512-0V4ICKnI1npglqrMSDK2y8WxOdN79DkMoIexzY3P+jr2wNfbB4J81BgjFfHsj18wBsV7FdKCWyCHcezzH0xlyg==
|
||||||
|
|
||||||
moment@^2.19.2, moment@^2.29.4:
|
moment@^2.29.4:
|
||||||
version "2.29.4"
|
version "2.29.4"
|
||||||
resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz"
|
resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz"
|
||||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||||
@@ -12075,7 +12075,7 @@ string-length@^2.0.0:
|
|||||||
astral-regex "^1.0.0"
|
astral-regex "^1.0.0"
|
||||||
strip-ansi "^4.0.0"
|
strip-ansi "^4.0.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -12093,6 +12093,15 @@ string-width@^1.0.1:
|
|||||||
is-fullwidth-code-point "^1.0.0"
|
is-fullwidth-code-point "^1.0.0"
|
||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
|
|
||||||
|
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
|
string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz"
|
resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz"
|
||||||
@@ -12193,7 +12202,7 @@ stringify-package@^1.0.1:
|
|||||||
resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz"
|
||||||
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
|
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -12221,6 +12230,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz"
|
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz"
|
||||||
@@ -13215,13 +13231,6 @@ vue-markdown@^2.2.4:
|
|||||||
markdown-it-task-lists "^2.0.1"
|
markdown-it-task-lists "^2.0.1"
|
||||||
markdown-it-toc-and-anchor "^4.1.2"
|
markdown-it-toc-and-anchor "^4.1.2"
|
||||||
|
|
||||||
vue-moment@^4.1.0:
|
|
||||||
version "4.1.0"
|
|
||||||
resolved "https://registry.npmmirror.com/vue-moment/-/vue-moment-4.1.0.tgz"
|
|
||||||
integrity sha512-Gzisqpg82ItlrUyiD9d0Kfru+JorW2o4mQOH06lEDZNgxci0tv/fua1Hl0bo4DozDV2JK1r52Atn/8QVCu8qQw==
|
|
||||||
dependencies:
|
|
||||||
moment "^2.19.2"
|
|
||||||
|
|
||||||
vue-password-strength-meter@^1.7.2:
|
vue-password-strength-meter@^1.7.2:
|
||||||
version "1.7.2"
|
version "1.7.2"
|
||||||
resolved "https://registry.npmmirror.com/vue-password-strength-meter/-/vue-password-strength-meter-1.7.2.tgz"
|
resolved "https://registry.npmmirror.com/vue-password-strength-meter/-/vue-password-strength-meter-1.7.2.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user