Merge branch 'dev' into pr@dev@perf_chat

This commit is contained in:
huailei
2023-12-12 18:51:43 +08:00
committed by GitHub
13 changed files with 11538 additions and 11594 deletions

View File

@@ -52,3 +52,12 @@ export function createJob(form) {
data: form
})
}
export function JobUploadFile(form) {
return request({
url: '/api/v1/ops/jobs/upload/',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data: form
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -205,6 +205,7 @@ export default {
width: 22px;
height: 22px;
transform: translateY(10%);
margin-left: 3px;
}
}
</style>

View File

@@ -8,7 +8,7 @@
:title="$tc('assets.PlatformProtocolConfig') + '' + protocol.name"
class="setting-dialog"
v-bind="$attrs"
width="70%"
width="800px"
v-on="$listeners"
>
<el-alert v-if="disabled && platformDetail" style="margin-bottom: 10px" type="success">

View File

@@ -1123,6 +1123,8 @@
"SaveAdhoc": "Save Adhoc",
"AdhocManage": "Adhoc manage",
"LastPublishedTime": "Last published",
"ExecuteCycle": "Execute cycle",
"ExpectedNextExecuteTime": "Expected next execute time",
"CloseConfirmMessage": "The file has changed, do you want to save it?",
"privilegeOnly": "Select only privileged accounts",
"UploadPlaybook": "Upload Playbook",
@@ -1174,7 +1176,11 @@
"Add": "Add",
"SuccessfulOperation": "Successful operation",
"Modify": "Modify",
"AdhocUpdate": "Update Adhoc"
"AdhocUpdate": "Update Adhoc",
"Transfer": "Transfer",
"UploadDir": "Upload Directory",
"RequiredUploadFile": "Please upload files",
"DuplicateFileExists": "Duplicate file exists"
},
"perms": {
"": "",
@@ -1299,8 +1305,8 @@
"AssetPermissionUpdate": "Asset permissions update",
"AssetUpdate": "Asset update",
"Assets": "Assets",
"LogsAudit": "Logs audit",
"SessionsAudit": "Sessions audit",
"LogsAudit": "Log audit",
"SessionsAudit": "Session audit",
"SessionList": "Session list",
"BatchCommand": "Batch Command",
"BatchScript": "Batch Script",
@@ -1358,7 +1364,7 @@
"DomainDetail": "Domain detail",
"DomainList": "Domains",
"DomainUpdate": "Domain update",
"FileManager": "File Manager",
"FileManager": "File manager",
"FtpLog": "FTP Logs",
"GatewayCreate": "Gateway create",
"GatewayUpdate": "Gateway update",
@@ -1367,11 +1373,11 @@
"LabelCreate": "Label create",
"LabelList": "Labels",
"LabelUpdate": "Label update",
"LoginLog": "Login Logs",
"LoginLog": "Login logs",
"MyApps": "My Apps",
"MyAssets": "My Assets",
"OperateLog": "Operation Logs",
"PasswordChangeLog": "Password Update Logs",
"MyAssets": "My assets",
"OperateLog": "Operation logs",
"PasswordChangeLog": "Password change logs",
"Perms": "Permissions",
"PersonalInformationImprovement": "Personal Information Improvement",
"PlatformCreate": "Platform create",
@@ -1423,7 +1429,7 @@
"UserUpdate": "User update",
"Users": "Users",
"WebFTP": "WebFTP",
"WebTerminal": "Web Terminal",
"WebTerminal": "Web terminal",
"Notifications": "Notifications",
"SiteMessageList": "Site message",
"UserLoginACL": "User Login ACL",
@@ -1456,7 +1462,7 @@
"AssetUserList": "Asset user",
"UserLoginACLUpdate": "Update User Login ACL",
"JobCreate": "Create job",
"JobExecutionLog": "Job Execution Log",
"JobExecutionLog": "Job execution log",
"JobList": "Job list",
"HostList": "Host list",
"UserLoginACLCreate": "Create User Login ACL",
@@ -1495,7 +1501,8 @@
"OnlineUserDevices": "OnlineUserDevices",
"More": "More ..",
"VirtualAppDetail": "Virtual App Detail",
"AppProviderDetail": "Provider Detail"
"AppProviderDetail": "Provider Detail",
"BulkTransfer": "Bulk transfer"
},
"rbac": {
"Permissions": "Permissions",
@@ -1623,13 +1630,13 @@
"Ticket": "Ticket",
"TaskList": "Task list",
"Announcement": "Announcement",
"Features": "Features enable",
"Features": "Features",
"PasswordRule": "Password rule",
"PasswordSecurity": "Password security",
"SessionSecurity": "Session security",
"AuthSecurity": "Auth security",
"MsgSubscribe": "Message subscribe",
"Message": "Message setting",
"Message": "Messages",
"ServerTime": "Server time",
"Custom": "Custom",
"CleanHelpText": "Regular cleanup tasks will be executed at 2 o'clock in the morning every day, and the cleaned data cannot be recovered",
@@ -1735,7 +1742,7 @@
"MailSend": "Mail send",
"LDAPServerInfo": "LDAP Server",
"LDAPUser": "LDAP User",
"ChatAI": "ChatAI",
"ChatAI": "Chat ai",
"InsecureCommandAlert": "Insecure command alert",
"helpText": {
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",

View File

@@ -1117,6 +1117,8 @@
"SaveAdhoc": "保存コマンド",
"AdhocManage": "コマンド管理",
"LastPublishedTime": "最終公開",
"ExecuteCycle": "実行サイクル",
"ExpectedNextExecuteTime": "次回実行予定時刻",
"CloseConfirmMessage": "ファイルが変更されました,保存しますか?",
"privilegeOnly": "特権アカウントのみを選択",
"UploadPlaybook": "アップロード Playbook",
@@ -1172,7 +1174,11 @@
"Add": "追加",
"SuccessfulOperation": "成功した操作",
"Modify": "改訂",
"AdhocUpdate": "更新コマンド"
"AdhocUpdate": "更新コマンド",
"Transfer": "ファイルを転送する",
"UploadDir": "アップロードディレクトリ",
"RequiredUploadFile": "ファイルをアップロードしてください!",
"DuplicateFileExists": "重複したファイルが存在する"
},
"perms": {
"": "",
@@ -1487,7 +1493,8 @@
"CloudCreate": "アセット作成 - クラウドプラットフォーム",
"DatabaseUpdate": "アセット更新 - データベース",
"CloudUpdate": "アセット更新 - クラウドプラットフォーム",
"More": "さらに.."
"More": "さらに..",
"BulkTransfer": "bulk transfer"
},
"rbac": {
"Permissions": "権限",
@@ -1735,7 +1742,7 @@
"MailSend": "メール送信",
"LDAPServerInfo": "LDAPサーバー",
"LDAPUser": "LDAPユーザー",
"ChatAI": "ChatAI",
"ChatAI": "チャットAI",
"helpText": {
"TempPassword": "一時パスワードの有効期間は300秒で、使用後すぐに失効します",
"ApiKeyList": "Api keyを使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",

View File

@@ -1142,6 +1142,8 @@
"AdhocDetail": "命令详情",
"Queue": "队列",
"State": "状态",
"ExecuteCycle": "执行周期",
"ExpectedNextExecuteTime": "预计下次执行时间",
"LastPublishedTime": "最后发布时间",
"Variable": "变量",
"Description": "描述",
@@ -1161,7 +1163,11 @@
"Comment": "备注",
"History": "执行历史",
"UseParameterDefine": "定义参数",
"TaskDispatch": "任务下发成功"
"TaskDispatch": "任务下发成功",
"Transfer": "传输",
"UploadDir": "上传目录",
"RequiredUploadFile": "请上传文件!",
"DuplicateFileExists": "存在同名文件"
},
"perms": {
"": "",
@@ -1472,7 +1478,8 @@
"CannotAccess": "无法访问当前页面",
"GoHomePage": "去往首页",
"VirtualAppDetail": "虚拟应用详情",
"AppProviderDetail": "应用提供者详情"
"AppProviderDetail": "应用提供者详情",
"BulkTransfer": "批量传输"
},
"rbac": {
"Permissions": "权限",
@@ -1730,7 +1737,7 @@
"MailSend": "邮件发送",
"LDAPServerInfo": "LDAP 服务器",
"LDAPUser": "LDAP 用户",
"ChatAI": "ChatAI",
"ChatAI": "聊天 AI",
"helpText": {
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
"ApiKeyList": "使用 Api key 签名请求头进行认证,每个请求的头部是不一样的, 相对于 Token 方式,更加安全,请查阅文档使用;<br>为降低泄露风险Secret 仅在生成时可以查看, 每个用户最多支持创建 10 个",

View File

@@ -62,17 +62,29 @@ export default {
]
},
{
path: 'external-elfinder',
path: '/workbench/file-manager',
name: 'FileManager',
component: empty,
meta: {
title: i18n.t('route.FileManager'),
icon: 'file',
permissions: ['rbac.view_filemanager']
},
children: [
{
path: 'bulk-Transfer',
name: 'BulkTransfer',
component: () => import('@/views/ops/File/index'),
meta: {
title: i18n.t('route.BulkTransfer'),
permissions: ['rbac.view_filemanager']
}
},
{
path: `${BASE_URL}/koko/elfinder/sftp/`,
name: '',
meta: {
title: i18n.t('route.FileManager'),
icon: 'file',
activeMenu: '/assets',
permissions: ['rbac.view_filemanager']
}

View File

@@ -303,3 +303,8 @@ input[type=file] {
.el-dialog__title {
font-size: 16px;
}
.el-input--mini .el-input__inner::placeholder {
font-size: 13px;
font-weight: 400;
}

View File

@@ -1,18 +1,18 @@
<template>
<el-row :gutter="10">
<el-col :span="18">
<OfflineList :url="url" />
<BaseList :url="url" :columns-show="columnsShow" />
</el-col>
</el-row>
</template>
<script>
import OfflineList from '@/views/sessions/SessionList/OfflineList.vue'
import BaseList from '@/views/sessions/SessionList/BaseList.vue'
export default {
name: 'AssetsSession',
components: {
OfflineList
BaseList
},
props: {
object: {
@@ -22,7 +22,14 @@ export default {
},
data() {
return {
url: `/api/v1/terminal/sessions/?asset_id=${this.object.id}&order=-date_end&is_finished=1`
url: `/api/v1/terminal/sessions/?asset_id=${this.object.id}&order=is_finished,-date_end`,
columnsShow: {
min: ['id'],
default: [
'id', 'user', 'asset', 'account', 'remote_addr', 'protocol',
'command_amount', 'date_start', 'duration'
]
}
}
}

View File

@@ -0,0 +1,479 @@
<template>
<Page>
<TreeTable ref="TreeTable" :tree-setting="treeSetting">
<template slot="table">
<div class="transition-box" style="width: calc(100% - 17px);">
<div class="upload_input">
<el-button
:disabled="run_button.disabled"
:type="run_button.el&&run_button.el.type"
size="mini"
style="display: inline-block; margin: 0 2px"
@click="run_button.callback()"
>
<i :class="run_button.icon" style="margin-right: 4px;" />{{ run_button.name }}
</el-button>
</div>
<div class="upload_input">{{ $t('users.Users') }}:</div>
<div class="upload_input">
<el-autocomplete
v-model="runas_input.value"
:placeholder="runas_input.placeholder"
:fetch-suggestions="runas_input.el.query"
style="display: inline-block; margin: 0 2px"
size="mini"
@select="runas_input.callback(runas_input.value)"
@change="runas_input.callback(runas_input.value)"
/>
</div>
<div class="upload_input">{{ $t('ops.UploadDir') }}:</div>
<div class="upload_input">
<el-input
v-if="dst_path_input.type==='input'"
v-model="dst_path"
:placeholder="dst_path_input.placeholder"
size="mini"
@change="dst_path_input.callback(dst_path_input.value)"
/>
</div>
<div
class="file-uploader"
>
<el-card>
<el-upload
v-if="ready"
ref="upload"
:value.sync="files"
:auto-upload="false"
:on-change="onFileChange"
:on-remove="onFileChange"
drag
multiple
action=""
>
<i class="el-icon-upload" />
<div class="el-upload__text">
{{ $t('common.imExport.dragUploadFileInfo') }}
</div>
</el-upload>
<el-progress v-if="ShowProgress" :percentage="progressLength" />
</el-card>
</div>
<b>{{ $tc('ops.output') }}:</b>
<span v-if="executionInfo.status" style="float: right">
<span>
<span><b>{{ $tc('common.Status') }}: </b></span>
<span
:class="{'status_success':executionInfo.status==='success',
'status_warning':executionInfo.status==='timeout',
'status_danger':executionInfo.status==='failed'
}"
>{{ $tc('ops.' + executionInfo.status) }}</span>
</span>
<span>
<span><b>{{ $tc('ops.timeDelta') }}: </b></span>
<span>{{ executionInfo.timeCost.toFixed(2) }}</span>
</span>
</span>
<div style="border-left: 20px white solid">
<Term
ref="xterm"
:show-tool-bar="true"
/>
</div>
<div style="display: flex;margin-top:10px;justify-content: space-between" />
</div>
</template>
</TreeTable>
</Page>
</template>
<script>
import { TreeTable } from '@/components'
import Term from '@/components/Widgets/Term'
import Page from '@/layout/components/Page'
import { createJob, getJob, getTaskDetail, JobUploadFile } from '@/api/ops'
export default {
name: 'BulkTransfer',
components: {
TreeTable,
Page,
Term
},
data() {
return {
ready: true,
currentStatus: '',
currentTaskId: '',
executionInfo: {
status: '',
timeCost: 0,
cancel: 0
},
DataZTree: 0,
runas: '',
dst_path: '',
run_button: {
type: 'button',
name: this.$t('ops.Transfer'),
align: 'left',
icon: 'fa fa-play',
disabled: this.$store.getters.currentOrgIsRoot,
el: {
type: 'primary'
},
callback: () => {
this.execute()
}
},
runas_input: {
name: this.$t('ops.runAs'),
align: 'left',
value: '',
placeholder: this.$tc('ops.EnterRunUser'),
el: {
autoComplete: true,
query: (query, cb) => {
const { hosts, nodes } = this.getSelectedNodesAndHosts()
this.$axios.post('/api/v1/ops/username-hints/', {
nodes: nodes,
assets: hosts,
query: query
}).then(data => {
const ns = data.map(item => {
return { value: item.username }
})
cb(ns)
})
}
},
options: [],
callback: (option) => {
this.runas = option
}
},
dst_path_input: {
type: 'input',
name: this.$t('ops.runningPath'),
align: 'left',
value: '',
placeholder: this.$tc('ops.EnterRunningPath'),
callback: (val) => {
this.chdir = val
}
},
files: null,
src_paths: [],
treeSetting: {
treeUrl: '/api/v1/perms/users/self/nodes/children-with-assets/tree/',
searchUrl: '/api/v1/perms/users/self/assets/tree/',
showRefresh: true,
showMenu: false,
showSearch: true,
check: {
enable: true
},
view: {
dblClickExpand: false,
showLine: true
}
},
iShowTree: true,
progressLength: 0,
ShowProgress: false,
upload_interval: null
}
},
computed: {
xterm() {
return this.$refs.xterm.xterm
},
ztree() {
return this.$refs.TreeTable.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree
}
},
mounted() {
this.enableWS()
this.initData()
},
methods: {
async initData() {
this.recoverStatus()
},
recoverStatus() {
if (this.$route.query.taskId) {
this.currentTaskId = this.$route.query.taskId
getTaskDetail(this.currentTaskId).then(data => {
getJob(data.job_id).then(res => {
this.runas_input.value = res.runas
this.runas_input.callback(res.runas)
this.executionInfo.status = data['status']
this.executionInfo.timeCost = data['time_cost']
this.setCostTimeInterval()
this.writeExecutionOutput()
})
})
}
},
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ops/tasks/log/'
const wsURL = scheme + '://' + document.location.hostname + port + url
this.ws = new WebSocket(wsURL)
this.ws.onerror = (e) => {
this.xterm.write(this.wrapperError('Connect websocket server error'))
}
this.setWsCallback()
},
setWsCallback() {
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data)
if (data.hasOwnProperty('message')) {
let message = data.message
message = message.replace(/Task ops\.tasks\.run_ops_job_execution.*/, '')
this.xterm.write(message)
}
if (data?.event === 'end') {
setTimeout(() => {
clearInterval(this.executionInfo.cancel)
this.execute_stop()
this.getTaskStatus()
}, 500)
}
}
},
getTaskStatus() {
getTaskDetail(this.currentTaskId).then(data => {
this.executionInfo.status = data['status']
if (this.executionInfo.status === 'success') {
this.$message.success(this.$tc('terminal.UploadSucceed'))
clearInterval(this.upload_interval)
this.progressLength = 100
this.ShowProgress = true
}
})
},
wrapperError(msg) {
return `\r\n${msg}\r\n`
},
writeExecutionOutput() {
let msg = this.$t('assets.Pending')
this.xterm.write(msg)
msg = JSON.stringify({ task: this.currentTaskId })
this.ws.send(msg)
},
getSelectedNodes() {
return this.ztree.getCheckedNodes().filter(node => {
const status = node.getCheckStatus()
return node.id !== 'search' && status.half === false
})
},
setCostTimeInterval() {
this.run_button.icon = 'fa fa-spinner fa-spin'
this.run_button.disabled = true
this.executionInfo.cancel = setInterval(() => {
this.executionInfo.timeCost += 0.1
}, 100)
},
getSelectedNodesAndHosts() {
const hosts = this.getSelectedNodes().filter((item) => {
return item.meta.type !== 'node'
}).map(function(node) {
return node.id
})
const nodes = this.getSelectedNodes().filter((item) => {
return item.meta.type === 'node'
}).map(function(node) {
return node.meta.data.id
})
return { hosts, nodes }
},
truncateFileName(fullName) {
const maxLength = 140 // 显示的最大字符数
if (fullName.length <= maxLength) {
return fullName
}
const firstPart = fullName.slice(0, maxLength / 2)
const secondPart = fullName.slice(-maxLength / 2)
return firstPart + '...' + secondPart
},
renderSameFile(file, fileList) {
const filenameList = fileList.map((file) => file.name)
const filenameCount = _.countBy(filenameList)
// 找出同名文件
this.$nextTick(() => {
const fileElementList = document.getElementsByClassName('el-upload-list__item-name')
if (fileElementList && fileElementList.length > 0) {
for (const ele of fileElementList) {
if (filenameCount[ele.outerText] > 1) {
this.$message.error(this.$tc('ops.DuplicateFileExists'))
ele.style = 'background-color:var(--color-danger)'
} else {
ele.style = ''
}
}
}
})
},
onFileChange(file, fileList) {
file.name = this.truncateFileName(file.name)
this.files = fileList
this.renderSameFile(file, fileList)
},
execute() {
const { hosts, nodes } = this.getSelectedNodesAndHosts()
if (!this.files) {
this.$message.error(this.$tc('ops.RequiredUploadFile'))
return
}
if (hosts.length === 0 && nodes.length === 0) {
this.$message.error(this.$tc('ops.RequiredAssetOrNode'))
return
}
if (!this.runas) {
this.$message.error(this.$tc('ops.RequiredRunas'))
return
}
const data = {
assets: hosts,
nodes: nodes,
module: 'shell',
args: JSON.stringify({ dst_path: this.dst_path }),
type: 'upload_file',
runas: this.runas,
runas_policy: 'skip',
instant: false,
is_periodic: false,
timeout: -1
}
if (this.chdir) {
data.chdir = this.chdir
}
createJob(data).then(res => {
this.progressLength = 0
this.ShowProgress = true
const form = new FormData()
for (const file of this.files) {
form.append('files', file.raw)
form.append('job_id', res.id)
}
this.upload_interval = setInterval(() => {
if (this.progressLength >= 99) {
clearInterval(this.upload_interval)
return
}
this.progressLength += 1
}, 100)
JobUploadFile(form).then(res => {
this.executionInfo.timeCost = 0
this.executionInfo.status = 'running'
this.currentTaskId = res.task_id
this.$router.replace({ query: { taskId: this.currentTaskId }})
this.setCostTimeInterval()
this.writeExecutionOutput()
}).catch(() => {
this.execute_stop()
})
})
},
execute_stop() {
this.executionInfo.timeCost = 0
this.progressLength = 0
this.ShowProgress = false
this.run_button.disabled = false
clearInterval(this.upload_interval)
this.run_button.icon = 'fa fa-play'
}
}
}
</script>
<style lang="scss" scoped>
.mini-button {
width: 12px;
float: right;
text-align: center;
padding: 5px 0;
background-color: var(--color-primary);
border-color: var(--color-primary);
color: #FFFFFF;
border-radius: 3px;
}
.el-tree {
background-color: inherit !important;
}
.mini {
margin-right: 5px;
width: 12px !important;
}
.auto-data-ztree {
overflow: auto;
}
.vue-codemirror-wrap ::v-deep .CodeMirror {
width: 600px;
height: 100px;
border: 1px solid #eee;
}
.tree-box {
margin-right: 2px;
border: 1px solid #e0e0e0;
}
.status_success {
color: var(--color-success);
}
.status_warning {
color: var(--color-warning);
}
.status_danger {
color: var(--color-danger);
}
.upload_input {
display: inline-block;
margin: 0 2px
}
.file-uploader {
margin: 10px 0 10px 0;
div > div:first-child {
display: flex;
}
> > > .el-upload {
width: 400px;
flex-direction: column;
.el-upload-dragger {
width: 100%;
}
}
> > > .el-upload-list {
margin-left: 20px;
padding: 0 10px 0 10px;
list-style: none;
width: 100%;
height: 180px;
}
> > > .el-upload-list:not(:empty) {
border: 1px dashed #d9d9d9;
overflow-y: auto;
}
}
</style>

View File

@@ -16,7 +16,7 @@ export default {
tableConfig: {
url: '/api/v1/ops/tasks/',
columns: [
'name', 'queue', 'count', 'state', 'date_last_publish'
'name', 'queue', 'count', 'state', 'date_last_publish', 'exec_cycle', 'next_exec_time'
],
columnsMeta: {
name: {
@@ -56,6 +56,20 @@ export default {
return row.last_published_time != null ? row.last_published_time : '-'
}
},
exec_cycle: {
label: this.$t('ops.ExecuteCycle'),
width: '120px',
formatter: (row) => {
return row.exec_cycle ? row.exec_cycle : '-'
}
},
next_exec_time: {
label: this.$t('ops.ExpectedNextExecuteTime'),
width: '210px',
formatter: (row) => {
return row.next_exec_time ? row.next_exec_time : '-'
}
},
count: {
width: '80px',
label: `${this.$t('ops.success')}/${this.$t('ops.total')}`,

22528
yarn.lock

File diff suppressed because it is too large Load Diff