mirror of
https://github.com/jumpserver/lina.git
synced 2025-11-07 09:58:38 +00:00
Compare commits
49 Commits
v4.10.0-lt
...
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 | ||
|
|
d48cb6b1f3 | ||
|
|
945ff8fc44 | ||
|
|
3a823c786e | ||
|
|
68a474644c | ||
|
|
c8b2ec9cdb | ||
|
|
67d4fdd175 | ||
|
|
0f40b38abe | ||
|
|
714350d40e | ||
|
|
d6ac0db0e6 | ||
|
|
ddae51cefc | ||
|
|
eb9f7c6cb5 | ||
|
|
a22f46087f | ||
|
|
2eedb361a0 | ||
|
|
d893964947 | ||
|
|
13838f66a9 | ||
|
|
6dccdae9b4 | ||
|
|
3f31fa9810 | ||
|
|
a17025bd3a | ||
|
|
f08ce0ee1a |
@@ -1,4 +1,4 @@
|
||||
FROM jumpserver/lina-base:20250508_085854 AS stage-build
|
||||
FROM jumpserver/lina-base:20250616_083043 AS stage-build
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
"vue-i18n": "^8.15.5",
|
||||
"vue-json-editor": "^1.4.3",
|
||||
"vue-markdown": "^2.2.4",
|
||||
"vue-moment": "^4.1.0",
|
||||
"vue-password-strength-meter": "^1.7.2",
|
||||
"vue-router": "3.0.6",
|
||||
"vue-select": "^3.9.5",
|
||||
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
},
|
||||
|
||||
createWatermark() {
|
||||
if (this.currentUser?.username && this.publicSettings?.SECURITY_WATERMARK_ENABLED && this.$store.getters.hasValidLicense) {
|
||||
if (this.currentUser?.username && this.publicSettings?.SECURITY_WATERMARK_ENABLED) {
|
||||
this.watermark = new Watermark({
|
||||
content: this.getWaterMarkContent(),
|
||||
width: this.publicSettings?.SECURITY_WATERMARK_WIDTH,
|
||||
|
||||
1
src/assets/img/cloud/smartx.svg
Normal file
1
src/assets/img/cloud/smartx.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1748326203303" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M10.24 605.184l839.168-481.28L1013.76 220.672v191.488L174.592 895.488 10.24 804.352z" fill="#0096FF" p-id="2854"></path><path d="M10.24 416.768V220.672l168.96-96.768 308.736 178.688-331.776 193.536zM541.184 717.312l331.264-195.072 141.312 88.064v194.048l-165.376 95.744z" fill="#25C764" p-id="2855"></path></svg>
|
||||
|
After Width: | Height: | Size: 645 B |
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="chat-action">
|
||||
<div v-if="hasPrompt" class="chat-action">
|
||||
<Select2
|
||||
v-model="select.value"
|
||||
:disabled="isLoading || isSelectDisabled"
|
||||
@@ -36,6 +36,10 @@ export default {
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hasPrompt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<!-- eslint-disable-next-line -->
|
||||
<div class="divider"></div>
|
||||
<p>
|
||||
<MessageText :message="item.reasoning" />
|
||||
<MessageText :message="item.reasoning" @insert-code="handleInsertCode" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
<span v-if="isServerError" class="error">
|
||||
{{ isServerError }}
|
||||
</span>
|
||||
<MessageText :message="item.result" />
|
||||
</div>
|
||||
<MessageText :message="item.result" :is-terminal="isTerminal" @insert-code="handleInsertCode" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
@@ -93,6 +92,10 @@ export default {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
isTerminal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -142,6 +145,9 @@ export default {
|
||||
if (value === 'copy') {
|
||||
copy(this.item.result.content)
|
||||
}
|
||||
},
|
||||
handleInsertCode(code) {
|
||||
this.$emit('insert-code', code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ export default {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
isTerminal: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -45,10 +49,10 @@ export default {
|
||||
this.init()
|
||||
},
|
||||
updated() {
|
||||
this.addCopyEvents()
|
||||
this.addEvents()
|
||||
},
|
||||
destroyed() {
|
||||
this.removeCopyEvents()
|
||||
this.removeEvents()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
@@ -69,26 +73,64 @@ export default {
|
||||
this.markdown.use(mdKatex, { blockClass: 'katexmath-block rounded-md', errorColor: ' #cc0000' })
|
||||
},
|
||||
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() {
|
||||
const copyBtn = document.querySelectorAll('.code-block-header__copy')
|
||||
copyBtn.forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const code = btn.parentElement?.nextElementSibling?.textContent
|
||||
if (code) {
|
||||
copy(code)
|
||||
}
|
||||
addEvents() {
|
||||
this.addBtnClickEvents('.code-block-header__copy', this.handlerClickCopy)
|
||||
this.addBtnClickEvents('.code-block-header__insert', this.handlerClickInsert)
|
||||
},
|
||||
|
||||
handlerClickCopy(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
|
||||
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) {
|
||||
const copyBtn = this.$refs.textRef.querySelectorAll('.code-block-header__copy')
|
||||
copyBtn.forEach((btn) => {
|
||||
btn.removeEventListener('click', () => {
|
||||
})
|
||||
})
|
||||
this.removeBtnClickEvent('.code-block-header__copy')
|
||||
this.addBtnClickEvents('.code-block-header__insert')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +140,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.markdown-body {
|
||||
font-size: 13px;
|
||||
max-width: 300px;;
|
||||
|
||||
&::v-deep p {
|
||||
margin-bottom: 0 !important;
|
||||
@@ -115,26 +158,46 @@ export default {
|
||||
|
||||
&::v-deep .code-block-wrapper {
|
||||
background: #1F2329;
|
||||
padding: 2px 6px;
|
||||
padding: 0;
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.code-block-body {
|
||||
padding: 5px 10px 0;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
;
|
||||
|
||||
.code-block-header {
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
background: #353946;
|
||||
color: #c2d1e1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.code-block-header__copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
.code-block-header__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
color: #6e747b;
|
||||
.code-block-header__copy {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #6e747b;
|
||||
}
|
||||
}
|
||||
|
||||
.code-block-header__insert {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #6e747b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +241,7 @@ export default {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</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 class="input-box">
|
||||
<el-button
|
||||
@@ -28,7 +28,7 @@
|
||||
size="small"
|
||||
@click="onStopHandle"
|
||||
>{{ $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>
|
||||
</template>
|
||||
@@ -68,7 +68,10 @@ export default {
|
||||
prompt: '',
|
||||
conversationId: '',
|
||||
showIntroduction: false,
|
||||
introduction: []
|
||||
introduction: [],
|
||||
terminalContext: null,
|
||||
isTerminal: false,
|
||||
sessionChat: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -100,6 +103,9 @@ export default {
|
||||
this.showIntroduction = true
|
||||
this.conversationId = ''
|
||||
this.$refs.chatInput.select.value = ''
|
||||
if (this.terminalContext) {
|
||||
this.prompt = this.terminalContext.content || ''
|
||||
}
|
||||
const chat = {
|
||||
message: {
|
||||
content: this.$t('ChatHello'),
|
||||
@@ -150,6 +156,32 @@ export default {
|
||||
addMessageToActiveChat(data)
|
||||
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) {
|
||||
this.showIntroduction = false
|
||||
this.socket = ws || {}
|
||||
@@ -204,6 +236,15 @@ export default {
|
||||
sendIntroduction(item) {
|
||||
this.showIntroduction = false
|
||||
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 { ws } from '@/utils/socket'
|
||||
import DrawerPanel from '@/components/Apps/DrawerPanel/index.vue'
|
||||
import { ObjectLocalStorage } from '@/utils/common'
|
||||
|
||||
const aiPannelLocalStorage = new ObjectLocalStorage('ai_panel_settings')
|
||||
export default {
|
||||
components: {
|
||||
DrawerPanel,
|
||||
@@ -76,12 +78,15 @@ export default {
|
||||
robotUrl: require('@/assets/img/robot-assistant.png'),
|
||||
height: '400px',
|
||||
expanded: false,
|
||||
clientOffset: {}
|
||||
clientOffset: {},
|
||||
currentTerminalContent: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
mounted() {
|
||||
const expanded = aiPannelLocalStorage.get('expanded')
|
||||
this.updateExpandedState(expanded)
|
||||
this.handlePostMessage()
|
||||
},
|
||||
methods: {
|
||||
@@ -89,6 +94,17 @@ export default {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data === 'show-chat-panel') {
|
||||
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)
|
||||
},
|
||||
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)
|
||||
},
|
||||
initWebSocket() {
|
||||
@@ -107,12 +128,20 @@ export default {
|
||||
this.$refs.drawer.show = false
|
||||
},
|
||||
expandFull() {
|
||||
this.height = '100%'
|
||||
this.expanded = true
|
||||
this.updateExpandedState(true)
|
||||
this.save_pannel_settings()
|
||||
},
|
||||
compress() {
|
||||
this.height = '400px'
|
||||
this.expanded = false
|
||||
this.updateExpandedState(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() {
|
||||
this.active = 'chat'
|
||||
|
||||
@@ -42,11 +42,12 @@ export default {
|
||||
hasCreate: true,
|
||||
hasSearch: true,
|
||||
hasRefresh: true,
|
||||
hasBulkDelete: false,
|
||||
hasBulkDelete: true,
|
||||
hasBulkUpdate: false,
|
||||
hasLeftActions: true,
|
||||
hasRightActions: true,
|
||||
canCreate: this.$hasPerm('settings.change_security')
|
||||
canCreate: this.$hasPerm('settings.change_security'),
|
||||
canBulkDelete: this.$hasPerm('settings.change_security')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
:src="faceCaptureUrl"
|
||||
allow="camera"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
style="width: 100%; height: 800px;border: none;"
|
||||
style="width: 100%; height: 600px;border: none;"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -99,7 +99,7 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="subTypeSelected === 'face'"
|
||||
:disabled="isFaceCaptureVisible"
|
||||
v-show="!isFaceCaptureVisible"
|
||||
class="confirm-btn"
|
||||
size="mini"
|
||||
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']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
var_name: {
|
||||
helpTextAsTip: false
|
||||
},
|
||||
text_default_value: {
|
||||
label: this.$t('DefaultValue'),
|
||||
hidden: (formValue) => {
|
||||
|
||||
@@ -140,14 +140,11 @@ export default {
|
||||
this._cleanFormValue(this.iForm, this.remoteMeta)
|
||||
},
|
||||
setFieldError(name, error) {
|
||||
error = error.replace(/[。.]+$/, '')
|
||||
const field = this.totalFields.find((v) => v.prop === name)
|
||||
if (!field) {
|
||||
return
|
||||
}
|
||||
if (field.attrs.error === error) {
|
||||
error += '.'
|
||||
}
|
||||
|
||||
if (typeof error === 'string') {
|
||||
field.el.errors = error
|
||||
field.attrs.error = error
|
||||
|
||||
@@ -21,13 +21,16 @@ export class TableColumnsGenerator {
|
||||
}
|
||||
|
||||
dynamicActionWidth() {
|
||||
if (i18n.locale === 'en') {
|
||||
console.log(i18n.locale)
|
||||
if (i18n.locale === 'zh-hans' || i18n.locale === 'zh-hant') {
|
||||
return '100px'
|
||||
}
|
||||
|
||||
if (i18n.locale === 'ja' || i18n.locale === 'ko') {
|
||||
return '120px'
|
||||
}
|
||||
if (i18n.locale === 'pt-br') {
|
||||
return '160px'
|
||||
}
|
||||
return '100px'
|
||||
|
||||
return '160px'
|
||||
}
|
||||
|
||||
generateColumns() {
|
||||
|
||||
@@ -24,16 +24,17 @@ const formatterArgsDefault = {
|
||||
true: 'text-primary',
|
||||
false: 'text-danger'
|
||||
},
|
||||
textChoices: {
|
||||
true: i18n.t('Yes'),
|
||||
false: i18n.t('No')
|
||||
},
|
||||
getKey({ row, cellValue }) {
|
||||
return (cellValue && typeof cellValue === 'object') ? cellValue.value : cellValue
|
||||
},
|
||||
getText({ 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 }) {
|
||||
const key = this.getKey({ row, cellValue })
|
||||
|
||||
@@ -178,7 +178,18 @@ export default {
|
||||
return
|
||||
}
|
||||
if (currentNode) {
|
||||
currentNode.name = currentNode.meta.data.value
|
||||
// 从节点名称中提取资产数量并保存
|
||||
const nameMatch = currentNode.name.match(/^(.+?)\s*\((\d+)\)$/)
|
||||
|
||||
if (nameMatch) {
|
||||
const pureName = nameMatch[1]
|
||||
const assetsAmount = parseInt(nameMatch[2])
|
||||
|
||||
currentNode.name = pureName
|
||||
currentNode.meta.data['assetsAmount'] = assetsAmount // 保存资产数量,确保重命名时不会丢失
|
||||
} else {
|
||||
currentNode.name = currentNode.meta.data.value
|
||||
}
|
||||
}
|
||||
this.zTree.editName(currentNode)
|
||||
},
|
||||
@@ -237,13 +248,14 @@ export default {
|
||||
if (isCancel) {
|
||||
return
|
||||
}
|
||||
|
||||
const originalAssetsAmount = treeNode.meta.data['assetsAmount'] || 0
|
||||
|
||||
this.$axios.patch(url, { 'value': treeNode.name }).then(res => {
|
||||
let assetsAmount = treeNode.meta.data['assetsAmount']
|
||||
if (!assetsAmount) {
|
||||
assetsAmount = 0
|
||||
}
|
||||
treeNode.name = treeNode.name + ' (' + assetsAmount + ')'
|
||||
treeNode.meta.data = res
|
||||
treeNode.name = treeNode.name + ' (' + originalAssetsAmount + ')'
|
||||
treeNode.meta.data = Object.assign({}, treeNode.meta.data, res)
|
||||
treeNode.meta.data['assetsAmount'] = originalAssetsAmount
|
||||
|
||||
this.zTree.updateNode(treeNode)
|
||||
this.$message.success(this.$tc('UpdateSuccessMsg'))
|
||||
}).finally(() => {
|
||||
|
||||
@@ -24,6 +24,9 @@ import { message } from '@/utils/message'
|
||||
import xss from '@/utils/xss'
|
||||
import ElTableTooltipPatch from '@/utils/elTableTooltipPatch.js'
|
||||
import VSanitize from 'v-sanitize'
|
||||
import moment from 'moment'
|
||||
moment.locale('zh-cn')
|
||||
|
||||
/**
|
||||
* If you don't want to use mock-server
|
||||
* you want to use MockJs for mock api
|
||||
@@ -50,11 +53,7 @@ Vue.config.productionTip = false
|
||||
Vue.use(VueCookie)
|
||||
window.$cookie = VueCookie
|
||||
|
||||
const moment = require('moment')
|
||||
require('moment/locale/zh-cn')
|
||||
Vue.use(require('vue-moment'), {
|
||||
moment
|
||||
})
|
||||
Vue.prototype.$moment = moment
|
||||
|
||||
Vue.use(VueLogger, loggerOptions)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ const actions = {
|
||||
link.href = faviconURL
|
||||
}
|
||||
// 动态修改Title
|
||||
document.title = data['INTERFACE']['login_title']
|
||||
document.title = data?.INTERFACE?.login_title || ''
|
||||
}
|
||||
const themeColors = data?.INTERFACE?.theme_info?.colors || {}
|
||||
commit('SET_PUBLIC_SETTINGS', data)
|
||||
|
||||
@@ -8,7 +8,9 @@ for (let i = 0; i < chars.length; i++) {
|
||||
|
||||
const encode = function(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) {
|
||||
base64url += chars[bytes[i] >> 2]
|
||||
@@ -17,7 +19,7 @@ const encode = function(arraybuffer) {
|
||||
base64url += chars[bytes[i + 2] & 63]
|
||||
}
|
||||
|
||||
if ((len % 3) === 2) {
|
||||
if (len % 3 === 2) {
|
||||
base64url = base64url.substring(0, base64url.length - 1)
|
||||
} else if (len % 3 === 1) {
|
||||
base64url = base64url.substring(0, base64url.length - 2)
|
||||
@@ -28,8 +30,13 @@ const encode = function(arraybuffer) {
|
||||
|
||||
const decode = function(base64string) {
|
||||
const bufferLength = base64string.length * 0.75
|
||||
const len = base64string.length; let i; let p = 0
|
||||
let encoded1; let encoded2; let encoded3; let encoded4
|
||||
const len = base64string.length
|
||||
let i
|
||||
let p = 0
|
||||
let encoded1
|
||||
let encoded2
|
||||
let encoded3
|
||||
let encoded4
|
||||
|
||||
const bytes = new Uint8Array(bufferLength)
|
||||
|
||||
@@ -47,15 +54,16 @@ const decode = function(base64string) {
|
||||
return bytes.buffer
|
||||
}
|
||||
|
||||
const publicKeyCredentialToJSON = (pubKeyCred) => {
|
||||
const publicKeyCredentialToJSON = pubKeyCred => {
|
||||
if (pubKeyCred instanceof Array) {
|
||||
const arr = []
|
||||
for (const i of pubKeyCred) { arr.push(publicKeyCredentialToJSON(i)) }
|
||||
|
||||
for (const i of pubKeyCred) {
|
||||
arr.push(publicKeyCredentialToJSON(i))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
if (pubKeyCred instanceof ArrayBuffer) {
|
||||
if (pubKeyCred instanceof ArrayBuffer || pubKeyCred instanceof Uint8Array) {
|
||||
return encode(pubKeyCred)
|
||||
}
|
||||
|
||||
@@ -73,8 +81,7 @@ const publicKeyCredentialToJSON = (pubKeyCred) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
'decode': decode,
|
||||
'encode': encode,
|
||||
'publicKeyCredentialToJSON': publicKeyCredentialToJSON
|
||||
decode: decode,
|
||||
encode: encode,
|
||||
publicKeyCredentialToJSON: publicKeyCredentialToJSON
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
executed_amount: {
|
||||
formatter: (row) => {
|
||||
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: {
|
||||
@@ -101,7 +101,23 @@ export default {
|
||||
hasRefresh: true,
|
||||
hasExport: 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
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
192
src/views/accounts/AccountChangeSecret/AccountList.vue
Normal file
192
src/views/accounts/AccountChangeSecret/AccountList.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListTable ref="ListTable" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericListTable } from '@/layout/components'
|
||||
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'AccountChangeSecret',
|
||||
components: {
|
||||
GenericListTable
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
secretUrl: '',
|
||||
showViewSecretDialog: false,
|
||||
tableConfig: {
|
||||
url: '/api/v1/accounts/change-secret-status/',
|
||||
columns: [
|
||||
'execution_id', 'asset', 'account', 'status', 'ttl', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
asset: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
drawer: true,
|
||||
can: this.$hasPerm('assets.view_asset'),
|
||||
getTitle: ({ row }) => row.asset.name,
|
||||
getDrawerTitle: ({ row }) => row.asset.name,
|
||||
getRoute: ({ row }) => ({
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset.id },
|
||||
query: { tab: 'Basic' }
|
||||
})
|
||||
}
|
||||
},
|
||||
account: {
|
||||
label: vm.$t('Username'),
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
drawer: true,
|
||||
can: this.$hasPerm('accounts.view_account'),
|
||||
getTitle: ({ row }) => row.username,
|
||||
getDrawerTitle: ({ row }) => row.username,
|
||||
getRoute: ({ row }) => ({
|
||||
name: 'AssetAccountDetail',
|
||||
params: { id: row.id },
|
||||
query: { tab: 'Basic' }
|
||||
})
|
||||
}
|
||||
},
|
||||
ttl: {
|
||||
label: vm.$t('TTL')
|
||||
},
|
||||
execution_id: {
|
||||
width: '200px',
|
||||
label: vm.$t('ExecutionID'),
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
drawer: true,
|
||||
can: true,
|
||||
getTitle: ({ row }) => row.meta?.execution_id ? row.meta.execution_id : '-',
|
||||
getDrawerTitle: ({ row }) => row.meta?.execution_id,
|
||||
getRoute: ({ row }) => ({
|
||||
name: 'AccountChangeSecretExecutionDetail',
|
||||
params: { id: row.meta?.execution_id }
|
||||
})
|
||||
}
|
||||
},
|
||||
status: {
|
||||
width: '100px',
|
||||
label: vm.$t('Status'),
|
||||
formatter: (row) => {
|
||||
const statusMap = {
|
||||
queued: 'Queued',
|
||||
ready: 'Ready',
|
||||
processing: 'Processing'
|
||||
}
|
||||
|
||||
if (statusMap[row.meta.status]) {
|
||||
return <span>{ vm.$t(statusMap[row.meta.status]) }</span>
|
||||
}
|
||||
return <span>–</span>
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false,
|
||||
hasDelete: false,
|
||||
hasClone: false,
|
||||
moreActionsTitle: this.$t('More'),
|
||||
extraActions: [
|
||||
{
|
||||
name: 'Delete',
|
||||
title: this.$t('Delete'),
|
||||
can: this.$hasPerm('accounts.add_changesecretexecution'),
|
||||
type: 'danger',
|
||||
callback: ({ row }) => {
|
||||
this.$axios.delete(
|
||||
'/api/v1/accounts/change-secret-status/',
|
||||
{
|
||||
data: {
|
||||
account_ids: [row.id]
|
||||
}
|
||||
}
|
||||
)
|
||||
vm.$refs.ListTable.reloadTable()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasSearch: true,
|
||||
hasRefresh: true,
|
||||
hasLeftActions: true,
|
||||
hasRightActions: true,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasCreate: false,
|
||||
hasBulkDelete: false,
|
||||
hasBulkUpdate: false,
|
||||
searchConfig: {
|
||||
getUrlQuery: true,
|
||||
options: [
|
||||
{
|
||||
label: this.$t('AssetName'),
|
||||
value: 'asset_name'
|
||||
},
|
||||
{
|
||||
label: this.$t('ExecutionID'),
|
||||
value: 'execution_id'
|
||||
},
|
||||
{
|
||||
value: 'status',
|
||||
label: this.$t('Status'),
|
||||
type: 'choice',
|
||||
children: [
|
||||
{
|
||||
default: true,
|
||||
value: 'queued',
|
||||
label: this.$t('Queued')
|
||||
},
|
||||
{
|
||||
value: 'ready',
|
||||
label: this.$t('Ready')
|
||||
},
|
||||
{
|
||||
value: 'processing',
|
||||
label: this.$t('Processing')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
extraMoreActions: [
|
||||
{
|
||||
name: 'DeleteSelected',
|
||||
title: this.$t('DeleteSelected'),
|
||||
type: 'primary',
|
||||
fa: 'fa-retweet',
|
||||
can: ({ selectedRows }) => {
|
||||
return selectedRows.length > 0
|
||||
},
|
||||
callback: function({ selectedRows }) {
|
||||
const ids = selectedRows.map(v => {
|
||||
return v.id
|
||||
})
|
||||
this.$axios.delete(
|
||||
'/api/v1/accounts/change-secret-status/',
|
||||
{
|
||||
data: {
|
||||
account_ids: ids
|
||||
}
|
||||
}
|
||||
)
|
||||
vm.$refs.ListTable.reloadTable()
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -17,9 +17,8 @@ export default {
|
||||
BaseExecutionList
|
||||
},
|
||||
data() {
|
||||
const params = new URLSearchParams(this.$route.params).toString()
|
||||
return {
|
||||
url: `/api/v1/accounts/change-secret-executions/?${params}`,
|
||||
url: `/api/v1/accounts/change-secret-executions/`,
|
||||
detailRoute: 'AccountChangeSecretExecutionDetail',
|
||||
automationRoute: 'AccountChangeSecretDetail',
|
||||
resource: 'changesecretexecution',
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<script>
|
||||
import { TabPage } from '@/layout/components'
|
||||
import { mapGetters } from 'vuex'
|
||||
import store from '@/store'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@@ -38,13 +39,22 @@ export default {
|
||||
name: 'ChangeSecretRecord',
|
||||
hidden: () => !this.$hasPerm('accounts.view_changesecretrecord'),
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/ExecutionDetail/AccountChangeSecretRecord.vue')
|
||||
},
|
||||
{
|
||||
title: this.$t('ChangeSecretStatus'),
|
||||
name: 'ChangeSecretStatus',
|
||||
hidden: () => !this.$hasPerm('accounts.view_changesecretexecution') || !this.ChangeSecretAfterSessionEnd,
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountList.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['hasValidLicense'])
|
||||
...mapGetters(['hasValidLicense']),
|
||||
ChangeSecretAfterSessionEnd() {
|
||||
return store.getters.publicSettings?.CHANGE_SECRET_AFTER_SESSION_END
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('change-tab', this.handleChangeTab)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
:create-drawer="createDrawer"
|
||||
:detail-drawer="detailDrawer"
|
||||
:header-actions="headerActions"
|
||||
:resource="$tc('AccountDiscoverTask')"
|
||||
:resource="$tc('DiscoverAccountTask')"
|
||||
:table-config="tableConfig"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -104,7 +104,23 @@ export default {
|
||||
hasRefresh: true,
|
||||
hasExport: 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
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,35 @@ export default {
|
||||
updateSuccessNextRoute: { name: 'AccountTemplateList' }
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.setSecretTypeOptions()
|
||||
},
|
||||
methods: {
|
||||
setSecretTypeOptions() {
|
||||
const choices = [
|
||||
{
|
||||
label: this.$t('Password'),
|
||||
value: 'password'
|
||||
},
|
||||
{
|
||||
label: this.$t('SSHKey'),
|
||||
value: 'ssh_key'
|
||||
},
|
||||
{
|
||||
label: this.$t('Token'),
|
||||
value: 'token'
|
||||
},
|
||||
{
|
||||
label: this.$t('AccessKey'),
|
||||
value: 'access_key'
|
||||
},
|
||||
{
|
||||
label: this.$t('ApiKey'),
|
||||
value: 'api_key'
|
||||
}
|
||||
]
|
||||
this.fieldsMeta.secret_type.options = choices
|
||||
},
|
||||
getObjectDone(obj) {
|
||||
if (['token', 'access_key', 'api_key'].includes(obj.secret_type.value)) {
|
||||
this.fieldsMeta.auto_push.el.disabled = true
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:detail-drawer="detailDrawer"
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:resource="$t('AccountTemplate')"
|
||||
/>
|
||||
<ViewSecret
|
||||
v-if="showViewSecretDialog"
|
||||
|
||||
@@ -155,11 +155,6 @@ export default {
|
||||
}
|
||||
},
|
||||
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
|
||||
|
||||
if (this.customActions) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<GenericListTable ref="listTable" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
<GenericListTable
|
||||
ref="listTable"
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:resource="$t('DetectTasks')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -163,6 +163,7 @@ export default {
|
||||
|
||||
::v-deep .el-drawer__body {
|
||||
padding: 0 20px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.platform-content {
|
||||
|
||||
@@ -28,19 +28,20 @@ export const fc = 'fc'
|
||||
export const scp = 'scp'
|
||||
export const apsara_stack = 'apsara_stack'
|
||||
export const lan = 'lan'
|
||||
export const smartx = 'smartx'
|
||||
|
||||
export const publicHostProviders = [
|
||||
aliyun, qcloud, qcloud_lighthouse, huaweicloud,
|
||||
baiducloud, jdcloud, kingsoftcloud, aws_china,
|
||||
aws_international, azure, azure_international,
|
||||
gcp, ucloud, volcengine
|
||||
gcp, ucloud, volcengine, smartx
|
||||
]
|
||||
|
||||
export const publicDBProviders = [aliyun]
|
||||
|
||||
export const privateCloudProviders = [
|
||||
vmware, qingcloud_private, huaweicloud_private, ctyun_private,
|
||||
openstack, zstack, nutanix, fc, scp, apsara_stack
|
||||
openstack, zstack, nutanix, fc, scp, apsara_stack, smartx
|
||||
]
|
||||
|
||||
export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
||||
@@ -164,6 +165,12 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
||||
attrs: ['access_key_id', 'access_key_secret', 'api_endpoint'],
|
||||
image: require('@/assets/img/cloud/zstack.svg')
|
||||
},
|
||||
[smartx]: {
|
||||
name: smartx,
|
||||
title: i18n.t('SmartX CloudTower'),
|
||||
attrs: ['username', 'password', 'api_endpoint'],
|
||||
image: require('@/assets/img/cloud/smartx.svg')
|
||||
},
|
||||
[fc]: {
|
||||
name: fc,
|
||||
title: i18n.t('FC'),
|
||||
|
||||
@@ -157,10 +157,6 @@ export default {
|
||||
assets: hosts,
|
||||
query: query
|
||||
}).then(data => {
|
||||
if (Array.isArray(data) && data.length === 0) {
|
||||
this.$message.info(`${this.$t('NoAccountFound')}`)
|
||||
return cb([])
|
||||
}
|
||||
const ns = data.map(item => {
|
||||
return { value: item.username }
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
name: 'Template'
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
const isClone = this?.$route?.query.clone_from !== undefined
|
||||
const isClone = this?.action === 'clone'
|
||||
if (isClone) {
|
||||
value?.variable.map((item) => {
|
||||
delete item.id
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
name: 'Template'
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
const isClone = this?.$route?.query.clone_from !== undefined
|
||||
const isClone = this?.action === 'clone'
|
||||
if (isClone) {
|
||||
value?.variable.map((item) => {
|
||||
delete item.id
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import UserSelect from '@/components/Apps/UserSelect'
|
||||
import AssetSelect from '@/components/Apps/AssetSelect'
|
||||
import AccountFormatter from './components/AccountFormatter'
|
||||
import { AllAccount } from '../const'
|
||||
@@ -49,14 +50,15 @@ export default {
|
||||
createSuccessNextRoute: { name: 'AssetPermissionDetail' },
|
||||
fieldsMeta: {
|
||||
users: {
|
||||
type: 'userSelect',
|
||||
component: UserSelect,
|
||||
rules: [{
|
||||
required: false
|
||||
}],
|
||||
el: {
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '/api/v1/users/users/?fields_size=mini',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name + '(' + item.username + ')', value: item.id }
|
||||
}
|
||||
}
|
||||
defaultPageSize: 300,
|
||||
baseUrl: '/api/v1/users/users/?fields_size=small'
|
||||
}
|
||||
},
|
||||
user_groups: {
|
||||
|
||||
@@ -22,6 +22,19 @@ export default {
|
||||
component: BoolTextReadonly
|
||||
}
|
||||
}
|
||||
},
|
||||
basic: {
|
||||
fieldsMeta: {
|
||||
lang: {
|
||||
on: {
|
||||
change: ([value], updateForm) => {
|
||||
this.$axios.get(`/core/i18n/${value}/`).then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ export default {
|
||||
},
|
||||
columnsMeta: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
columnsExclude: {
|
||||
type: Array,
|
||||
@@ -57,12 +58,15 @@ export default {
|
||||
id: {
|
||||
prop: 'id',
|
||||
label: this.$t('Number'),
|
||||
width: '80px',
|
||||
align: 'center',
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
drawer: true,
|
||||
can: this.$hasPerm('assets.view_asset'),
|
||||
getTitle: ({ row, col, cellValue, index }) => { return index + 1 },
|
||||
getTitle: ({ row, col, cellValue, index }) => {
|
||||
return index + 1
|
||||
},
|
||||
getDrawerTitle: ({ row }) => {
|
||||
return row.id
|
||||
},
|
||||
@@ -78,7 +82,9 @@ export default {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
drawer: true,
|
||||
getTitle: ({ row }) => { return row.user },
|
||||
getTitle: ({ row }) => {
|
||||
return row.user
|
||||
},
|
||||
getRoute: ({ row }) => {
|
||||
return {
|
||||
name: 'UserDetail',
|
||||
|
||||
@@ -25,7 +25,8 @@ export default {
|
||||
name: 'replay',
|
||||
title: this.$t('Replay'),
|
||||
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 }) {
|
||||
// 跳转到luna页面
|
||||
const replayUrl = '/luna/replay/' + row.id
|
||||
@@ -36,7 +37,7 @@ export default {
|
||||
name: 'download',
|
||||
title: this.$t('Download'),
|
||||
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 }) {
|
||||
// 跳转下载页面
|
||||
download(`/api/v1/terminal/sessions/${row.id}/replay/download/`)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:before-close="handleClose"
|
||||
:loading-status="!isFinished"
|
||||
:disabled-status="!isFinished"
|
||||
:show-cancel="false"
|
||||
:title="$tc('OfflineUpload')"
|
||||
v-bind="$attrs"
|
||||
@@ -104,6 +104,8 @@ export default {
|
||||
const error = err.response.data
|
||||
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
||||
this.$message.error(msg)
|
||||
}).finally(() => {
|
||||
this.$refs.upload.clearFiles()
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
ref="table"
|
||||
class="applet-host"
|
||||
:create-drawer="createDrawer"
|
||||
:resource="$t('AppletHosts')"
|
||||
v-bind="$data"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:before-close="handleClose"
|
||||
:loading-status="!isFinished"
|
||||
:disabled-status="!isFinished"
|
||||
:show-cancel="false"
|
||||
:title="$tc('OfflineUpload')"
|
||||
v-bind="$attrs"
|
||||
@@ -102,11 +102,9 @@ export default {
|
||||
const error = err.response.data
|
||||
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
|
||||
this.$message.error(msg)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
}).finally(() => {
|
||||
this.$refs.upload.clearFiles()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ export default {
|
||||
this.$t('Basic'),
|
||||
[
|
||||
'SECURITY_PASSWORD_EXPIRATION_TIME',
|
||||
'OLD_PASSWORD_HISTORY_LIMIT_COUNT'
|
||||
'OLD_PASSWORD_HISTORY_LIMIT_COUNT',
|
||||
'SECURITY_EXPIRED_TOKEN_RECORD_KEEP_DAYS'
|
||||
]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
'SECURITY_WATERMARK_HEIGHT',
|
||||
'SECURITY_WATERMARK_WIDTH',
|
||||
'SECURITY_WATERMARK_ROTATE'
|
||||
] : []
|
||||
] : ['SECURITY_WATERMARK_ENABLED']
|
||||
]
|
||||
],
|
||||
fieldsMeta: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="20" class="task-detail">
|
||||
<el-col :md="20" :sm="24">
|
||||
<DetailCard :items="detailCardItems" :title="cardTitle" />
|
||||
</el-col>
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
},
|
||||
{
|
||||
key: this.$t('LastPublishedTime'),
|
||||
value: this.object.last_published_time
|
||||
value: this.object.date_last_publish
|
||||
},
|
||||
{
|
||||
key: this.$t('Description'),
|
||||
@@ -55,7 +55,8 @@ export default {
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.task-detail /deep/ .item-value span {
|
||||
white-space: normal !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,7 +30,8 @@ export default {
|
||||
'',
|
||||
[
|
||||
'mysql_port', 'mariadb_port', 'postgresql_port',
|
||||
'redis_port', 'sqlserver_port', 'oracle_port_range'
|
||||
'redis_port', 'sqlserver_port', 'oracle_port',
|
||||
'mongodb_port'
|
||||
]
|
||||
],
|
||||
[this.$t('Other'), ['is_active', 'comment']]
|
||||
@@ -39,9 +40,6 @@ export default {
|
||||
host: {
|
||||
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
||||
},
|
||||
oracle_port_range: {
|
||||
disabled: true
|
||||
},
|
||||
is_active: {
|
||||
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:create-drawer="createDrawer"
|
||||
:resource="$t('Endpoint')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,7 +32,7 @@ export default {
|
||||
'name', 'host', 'actions',
|
||||
'http_port', 'https_port', 'ssh_port', 'rdp_port', 'vnc_port',
|
||||
'mysql_port', 'mariadb_port', 'postgresql_port',
|
||||
'redis_port', 'sqlserver_port', 'oracle_port', 'is_active'
|
||||
'redis_port', 'sqlserver_port', 'oracle_port', 'mongodb_port', 'is_active'
|
||||
]
|
||||
},
|
||||
columnsMeta: {
|
||||
@@ -43,7 +44,8 @@ export default {
|
||||
canUpdate: this.$hasPerm('terminal.change_endpoint'),
|
||||
updateRoute: 'EndpointUpdate',
|
||||
cloneRoute: 'EndpointCreate',
|
||||
canDelete: ({ row }) => row.id !== '00000000-0000-0000-0000-000000000001' && this.$hasPerm('terminal.delete_endpoint')
|
||||
canDelete: ({ row }) => row.id !== '00000000-0000-0000-0000-000000000001' &&
|
||||
this.$hasPerm('terminal.delete_endpoint')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:create-drawer="createDrawer"
|
||||
:resource="$t('EndpointRules')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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" />
|
||||
</GenericDetailPage>
|
||||
</template>
|
||||
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
},
|
||||
onMonitor() {
|
||||
const joinUrl = `/luna/monitor/${this.session.id}?ticket_id=${this.object.id}`
|
||||
window.open(joinUrl, 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
|
||||
window.open(joinUrl, '_blank', 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
|
||||
},
|
||||
onToggleLock() {
|
||||
const url = '/api/v1/terminal/tasks/toggle-lock-session-for-ticket/'
|
||||
|
||||
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"
|
||||
integrity sha512-0V4ICKnI1npglqrMSDK2y8WxOdN79DkMoIexzY3P+jr2wNfbB4J81BgjFfHsj18wBsV7FdKCWyCHcezzH0xlyg==
|
||||
|
||||
moment@^2.19.2, moment@^2.29.4:
|
||||
moment@^2.29.4:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz"
|
||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||
@@ -12075,7 +12075,7 @@ string-length@^2.0.0:
|
||||
astral-regex "^1.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"
|
||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -12093,6 +12093,15 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^1.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:
|
||||
version "2.1.1"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -12221,6 +12230,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "7.1.0"
|
||||
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-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:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.npmmirror.com/vue-password-strength-meter/-/vue-password-strength-meter-1.7.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user