mirror of
https://github.com/jumpserver/lina.git
synced 2025-09-01 15:07:43 +00:00
Merge branch 'dev' into pr@dev@perf_chat
This commit is contained in:
@@ -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 |
@@ -205,6 +205,7 @@ export default {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
transform: translateY(10%);
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -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">
|
||||
|
@@ -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",
|
||||
|
@@ -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を使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",
|
||||
|
@@ -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 个",
|
||||
|
@@ -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']
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
479
src/views/ops/File/index.vue
Normal file
479
src/views/ops/File/index.vue
Normal 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>
|
@@ -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')}`,
|
||||
|
Reference in New Issue
Block a user