mirror of
https://github.com/jumpserver/lina.git
synced 2025-09-07 01:41:10 +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
|
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;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
transform: translateY(10%);
|
transform: translateY(10%);
|
||||||
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
:title="$tc('assets.PlatformProtocolConfig') + ':' + protocol.name"
|
:title="$tc('assets.PlatformProtocolConfig') + ':' + protocol.name"
|
||||||
class="setting-dialog"
|
class="setting-dialog"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
width="70%"
|
width="800px"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
>
|
>
|
||||||
<el-alert v-if="disabled && platformDetail" style="margin-bottom: 10px" type="success">
|
<el-alert v-if="disabled && platformDetail" style="margin-bottom: 10px" type="success">
|
||||||
|
@@ -1123,6 +1123,8 @@
|
|||||||
"SaveAdhoc": "Save Adhoc",
|
"SaveAdhoc": "Save Adhoc",
|
||||||
"AdhocManage": "Adhoc manage",
|
"AdhocManage": "Adhoc manage",
|
||||||
"LastPublishedTime": "Last published",
|
"LastPublishedTime": "Last published",
|
||||||
|
"ExecuteCycle": "Execute cycle",
|
||||||
|
"ExpectedNextExecuteTime": "Expected next execute time",
|
||||||
"CloseConfirmMessage": "The file has changed, do you want to save it?",
|
"CloseConfirmMessage": "The file has changed, do you want to save it?",
|
||||||
"privilegeOnly": "Select only privileged accounts",
|
"privilegeOnly": "Select only privileged accounts",
|
||||||
"UploadPlaybook": "Upload Playbook",
|
"UploadPlaybook": "Upload Playbook",
|
||||||
@@ -1174,7 +1176,11 @@
|
|||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"SuccessfulOperation": "Successful operation",
|
"SuccessfulOperation": "Successful operation",
|
||||||
"Modify": "Modify",
|
"Modify": "Modify",
|
||||||
"AdhocUpdate": "Update Adhoc"
|
"AdhocUpdate": "Update Adhoc",
|
||||||
|
"Transfer": "Transfer",
|
||||||
|
"UploadDir": "Upload Directory",
|
||||||
|
"RequiredUploadFile": "Please upload files!",
|
||||||
|
"DuplicateFileExists": "Duplicate file exists"
|
||||||
},
|
},
|
||||||
"perms": {
|
"perms": {
|
||||||
"": "",
|
"": "",
|
||||||
@@ -1299,8 +1305,8 @@
|
|||||||
"AssetPermissionUpdate": "Asset permissions update",
|
"AssetPermissionUpdate": "Asset permissions update",
|
||||||
"AssetUpdate": "Asset update",
|
"AssetUpdate": "Asset update",
|
||||||
"Assets": "Assets",
|
"Assets": "Assets",
|
||||||
"LogsAudit": "Logs audit",
|
"LogsAudit": "Log audit",
|
||||||
"SessionsAudit": "Sessions audit",
|
"SessionsAudit": "Session audit",
|
||||||
"SessionList": "Session list",
|
"SessionList": "Session list",
|
||||||
"BatchCommand": "Batch Command",
|
"BatchCommand": "Batch Command",
|
||||||
"BatchScript": "Batch Script",
|
"BatchScript": "Batch Script",
|
||||||
@@ -1358,7 +1364,7 @@
|
|||||||
"DomainDetail": "Domain detail",
|
"DomainDetail": "Domain detail",
|
||||||
"DomainList": "Domains",
|
"DomainList": "Domains",
|
||||||
"DomainUpdate": "Domain update",
|
"DomainUpdate": "Domain update",
|
||||||
"FileManager": "File Manager",
|
"FileManager": "File manager",
|
||||||
"FtpLog": "FTP Logs",
|
"FtpLog": "FTP Logs",
|
||||||
"GatewayCreate": "Gateway create",
|
"GatewayCreate": "Gateway create",
|
||||||
"GatewayUpdate": "Gateway update",
|
"GatewayUpdate": "Gateway update",
|
||||||
@@ -1367,11 +1373,11 @@
|
|||||||
"LabelCreate": "Label create",
|
"LabelCreate": "Label create",
|
||||||
"LabelList": "Labels",
|
"LabelList": "Labels",
|
||||||
"LabelUpdate": "Label update",
|
"LabelUpdate": "Label update",
|
||||||
"LoginLog": "Login Logs",
|
"LoginLog": "Login logs",
|
||||||
"MyApps": "My Apps",
|
"MyApps": "My Apps",
|
||||||
"MyAssets": "My Assets",
|
"MyAssets": "My assets",
|
||||||
"OperateLog": "Operation Logs",
|
"OperateLog": "Operation logs",
|
||||||
"PasswordChangeLog": "Password Update Logs",
|
"PasswordChangeLog": "Password change logs",
|
||||||
"Perms": "Permissions",
|
"Perms": "Permissions",
|
||||||
"PersonalInformationImprovement": "Personal Information Improvement",
|
"PersonalInformationImprovement": "Personal Information Improvement",
|
||||||
"PlatformCreate": "Platform create",
|
"PlatformCreate": "Platform create",
|
||||||
@@ -1423,7 +1429,7 @@
|
|||||||
"UserUpdate": "User update",
|
"UserUpdate": "User update",
|
||||||
"Users": "Users",
|
"Users": "Users",
|
||||||
"WebFTP": "WebFTP",
|
"WebFTP": "WebFTP",
|
||||||
"WebTerminal": "Web Terminal",
|
"WebTerminal": "Web terminal",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"SiteMessageList": "Site message",
|
"SiteMessageList": "Site message",
|
||||||
"UserLoginACL": "User Login ACL",
|
"UserLoginACL": "User Login ACL",
|
||||||
@@ -1456,7 +1462,7 @@
|
|||||||
"AssetUserList": "Asset user",
|
"AssetUserList": "Asset user",
|
||||||
"UserLoginACLUpdate": "Update User Login ACL",
|
"UserLoginACLUpdate": "Update User Login ACL",
|
||||||
"JobCreate": "Create job",
|
"JobCreate": "Create job",
|
||||||
"JobExecutionLog": "Job Execution Log",
|
"JobExecutionLog": "Job execution log",
|
||||||
"JobList": "Job list",
|
"JobList": "Job list",
|
||||||
"HostList": "Host list",
|
"HostList": "Host list",
|
||||||
"UserLoginACLCreate": "Create User Login ACL",
|
"UserLoginACLCreate": "Create User Login ACL",
|
||||||
@@ -1495,7 +1501,8 @@
|
|||||||
"OnlineUserDevices": "OnlineUserDevices",
|
"OnlineUserDevices": "OnlineUserDevices",
|
||||||
"More": "More ..",
|
"More": "More ..",
|
||||||
"VirtualAppDetail": "Virtual App Detail",
|
"VirtualAppDetail": "Virtual App Detail",
|
||||||
"AppProviderDetail": "Provider Detail"
|
"AppProviderDetail": "Provider Detail",
|
||||||
|
"BulkTransfer": "Bulk transfer"
|
||||||
},
|
},
|
||||||
"rbac": {
|
"rbac": {
|
||||||
"Permissions": "Permissions",
|
"Permissions": "Permissions",
|
||||||
@@ -1623,13 +1630,13 @@
|
|||||||
"Ticket": "Ticket",
|
"Ticket": "Ticket",
|
||||||
"TaskList": "Task list",
|
"TaskList": "Task list",
|
||||||
"Announcement": "Announcement",
|
"Announcement": "Announcement",
|
||||||
"Features": "Features enable",
|
"Features": "Features",
|
||||||
"PasswordRule": "Password rule",
|
"PasswordRule": "Password rule",
|
||||||
"PasswordSecurity": "Password security",
|
"PasswordSecurity": "Password security",
|
||||||
"SessionSecurity": "Session security",
|
"SessionSecurity": "Session security",
|
||||||
"AuthSecurity": "Auth security",
|
"AuthSecurity": "Auth security",
|
||||||
"MsgSubscribe": "Message subscribe",
|
"MsgSubscribe": "Message subscribe",
|
||||||
"Message": "Message setting",
|
"Message": "Messages",
|
||||||
"ServerTime": "Server time",
|
"ServerTime": "Server time",
|
||||||
"Custom": "Custom",
|
"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",
|
"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",
|
"MailSend": "Mail send",
|
||||||
"LDAPServerInfo": "LDAP Server",
|
"LDAPServerInfo": "LDAP Server",
|
||||||
"LDAPUser": "LDAP User",
|
"LDAPUser": "LDAP User",
|
||||||
"ChatAI": "ChatAI",
|
"ChatAI": "Chat ai",
|
||||||
"InsecureCommandAlert": "Insecure command alert",
|
"InsecureCommandAlert": "Insecure command alert",
|
||||||
"helpText": {
|
"helpText": {
|
||||||
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",
|
"TempPassword": "For a while, there is a period of 300 seconds, failure immediately after use",
|
||||||
|
@@ -1117,6 +1117,8 @@
|
|||||||
"SaveAdhoc": "保存コマンド",
|
"SaveAdhoc": "保存コマンド",
|
||||||
"AdhocManage": "コマンド管理",
|
"AdhocManage": "コマンド管理",
|
||||||
"LastPublishedTime": "最終公開",
|
"LastPublishedTime": "最終公開",
|
||||||
|
"ExecuteCycle": "実行サイクル",
|
||||||
|
"ExpectedNextExecuteTime": "次回実行予定時刻",
|
||||||
"CloseConfirmMessage": "ファイルが変更されました,保存しますか?",
|
"CloseConfirmMessage": "ファイルが変更されました,保存しますか?",
|
||||||
"privilegeOnly": "特権アカウントのみを選択",
|
"privilegeOnly": "特権アカウントのみを選択",
|
||||||
"UploadPlaybook": "アップロード Playbook",
|
"UploadPlaybook": "アップロード Playbook",
|
||||||
@@ -1172,7 +1174,11 @@
|
|||||||
"Add": "追加",
|
"Add": "追加",
|
||||||
"SuccessfulOperation": "成功した操作",
|
"SuccessfulOperation": "成功した操作",
|
||||||
"Modify": "改訂",
|
"Modify": "改訂",
|
||||||
"AdhocUpdate": "更新コマンド"
|
"AdhocUpdate": "更新コマンド",
|
||||||
|
"Transfer": "ファイルを転送する",
|
||||||
|
"UploadDir": "アップロードディレクトリ",
|
||||||
|
"RequiredUploadFile": "ファイルをアップロードしてください!",
|
||||||
|
"DuplicateFileExists": "重複したファイルが存在する"
|
||||||
},
|
},
|
||||||
"perms": {
|
"perms": {
|
||||||
"": "",
|
"": "",
|
||||||
@@ -1487,7 +1493,8 @@
|
|||||||
"CloudCreate": "アセット作成 - クラウドプラットフォーム",
|
"CloudCreate": "アセット作成 - クラウドプラットフォーム",
|
||||||
"DatabaseUpdate": "アセット更新 - データベース",
|
"DatabaseUpdate": "アセット更新 - データベース",
|
||||||
"CloudUpdate": "アセット更新 - クラウドプラットフォーム",
|
"CloudUpdate": "アセット更新 - クラウドプラットフォーム",
|
||||||
"More": "さらに.."
|
"More": "さらに..",
|
||||||
|
"BulkTransfer": "bulk transfer"
|
||||||
},
|
},
|
||||||
"rbac": {
|
"rbac": {
|
||||||
"Permissions": "権限",
|
"Permissions": "権限",
|
||||||
@@ -1735,7 +1742,7 @@
|
|||||||
"MailSend": "メール送信",
|
"MailSend": "メール送信",
|
||||||
"LDAPServerInfo": "LDAPサーバー",
|
"LDAPServerInfo": "LDAPサーバー",
|
||||||
"LDAPUser": "LDAPユーザー",
|
"LDAPUser": "LDAPユーザー",
|
||||||
"ChatAI": "ChatAI",
|
"ChatAI": "チャットAI",
|
||||||
"helpText": {
|
"helpText": {
|
||||||
"TempPassword": "一時パスワードの有効期間は300秒で、使用後すぐに失効します",
|
"TempPassword": "一時パスワードの有効期間は300秒で、使用後すぐに失効します",
|
||||||
"ApiKeyList": "Api keyを使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",
|
"ApiKeyList": "Api keyを使用してリクエストヘッダに署名します。リクエストのヘッダごとに異なります。使用ドキュメントを参照してください",
|
||||||
|
@@ -1142,6 +1142,8 @@
|
|||||||
"AdhocDetail": "命令详情",
|
"AdhocDetail": "命令详情",
|
||||||
"Queue": "队列",
|
"Queue": "队列",
|
||||||
"State": "状态",
|
"State": "状态",
|
||||||
|
"ExecuteCycle": "执行周期",
|
||||||
|
"ExpectedNextExecuteTime": "预计下次执行时间",
|
||||||
"LastPublishedTime": "最后发布时间",
|
"LastPublishedTime": "最后发布时间",
|
||||||
"Variable": "变量",
|
"Variable": "变量",
|
||||||
"Description": "描述",
|
"Description": "描述",
|
||||||
@@ -1161,7 +1163,11 @@
|
|||||||
"Comment": "备注",
|
"Comment": "备注",
|
||||||
"History": "执行历史",
|
"History": "执行历史",
|
||||||
"UseParameterDefine": "定义参数",
|
"UseParameterDefine": "定义参数",
|
||||||
"TaskDispatch": "任务下发成功"
|
"TaskDispatch": "任务下发成功",
|
||||||
|
"Transfer": "传输",
|
||||||
|
"UploadDir": "上传目录",
|
||||||
|
"RequiredUploadFile": "请上传文件!",
|
||||||
|
"DuplicateFileExists": "存在同名文件"
|
||||||
},
|
},
|
||||||
"perms": {
|
"perms": {
|
||||||
"": "",
|
"": "",
|
||||||
@@ -1472,7 +1478,8 @@
|
|||||||
"CannotAccess": "无法访问当前页面",
|
"CannotAccess": "无法访问当前页面",
|
||||||
"GoHomePage": "去往首页",
|
"GoHomePage": "去往首页",
|
||||||
"VirtualAppDetail": "虚拟应用详情",
|
"VirtualAppDetail": "虚拟应用详情",
|
||||||
"AppProviderDetail": "应用提供者详情"
|
"AppProviderDetail": "应用提供者详情",
|
||||||
|
"BulkTransfer": "批量传输"
|
||||||
},
|
},
|
||||||
"rbac": {
|
"rbac": {
|
||||||
"Permissions": "权限",
|
"Permissions": "权限",
|
||||||
@@ -1730,7 +1737,7 @@
|
|||||||
"MailSend": "邮件发送",
|
"MailSend": "邮件发送",
|
||||||
"LDAPServerInfo": "LDAP 服务器",
|
"LDAPServerInfo": "LDAP 服务器",
|
||||||
"LDAPUser": "LDAP 用户",
|
"LDAPUser": "LDAP 用户",
|
||||||
"ChatAI": "ChatAI",
|
"ChatAI": "聊天 AI",
|
||||||
"helpText": {
|
"helpText": {
|
||||||
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
|
"TempPassword": "临时密码有效期为 300 秒,使用后立刻失效",
|
||||||
"ApiKeyList": "使用 Api key 签名请求头进行认证,每个请求的头部是不一样的, 相对于 Token 方式,更加安全,请查阅文档使用;<br>为降低泄露风险,Secret 仅在生成时可以查看, 每个用户最多支持创建 10 个",
|
"ApiKeyList": "使用 Api key 签名请求头进行认证,每个请求的头部是不一样的, 相对于 Token 方式,更加安全,请查阅文档使用;<br>为降低泄露风险,Secret 仅在生成时可以查看, 每个用户最多支持创建 10 个",
|
||||||
|
@@ -62,17 +62,29 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'external-elfinder',
|
path: '/workbench/file-manager',
|
||||||
|
name: 'FileManager',
|
||||||
component: empty,
|
component: empty,
|
||||||
meta: {
|
meta: {
|
||||||
|
title: i18n.t('route.FileManager'),
|
||||||
|
icon: 'file',
|
||||||
permissions: ['rbac.view_filemanager']
|
permissions: ['rbac.view_filemanager']
|
||||||
},
|
},
|
||||||
children: [
|
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/`,
|
path: `${BASE_URL}/koko/elfinder/sftp/`,
|
||||||
|
name: '',
|
||||||
meta: {
|
meta: {
|
||||||
title: i18n.t('route.FileManager'),
|
title: i18n.t('route.FileManager'),
|
||||||
icon: 'file',
|
|
||||||
activeMenu: '/assets',
|
activeMenu: '/assets',
|
||||||
permissions: ['rbac.view_filemanager']
|
permissions: ['rbac.view_filemanager']
|
||||||
}
|
}
|
||||||
|
@@ -303,3 +303,8 @@ input[type=file] {
|
|||||||
.el-dialog__title {
|
.el-dialog__title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-input--mini .el-input__inner::placeholder {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10">
|
||||||
<el-col :span="18">
|
<el-col :span="18">
|
||||||
<OfflineList :url="url" />
|
<BaseList :url="url" :columns-show="columnsShow" />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import OfflineList from '@/views/sessions/SessionList/OfflineList.vue'
|
import BaseList from '@/views/sessions/SessionList/BaseList.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AssetsSession',
|
name: 'AssetsSession',
|
||||||
components: {
|
components: {
|
||||||
OfflineList
|
BaseList
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
object: {
|
object: {
|
||||||
@@ -22,7 +22,14 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
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: {
|
tableConfig: {
|
||||||
url: '/api/v1/ops/tasks/',
|
url: '/api/v1/ops/tasks/',
|
||||||
columns: [
|
columns: [
|
||||||
'name', 'queue', 'count', 'state', 'date_last_publish'
|
'name', 'queue', 'count', 'state', 'date_last_publish', 'exec_cycle', 'next_exec_time'
|
||||||
],
|
],
|
||||||
columnsMeta: {
|
columnsMeta: {
|
||||||
name: {
|
name: {
|
||||||
@@ -56,6 +56,20 @@ export default {
|
|||||||
return row.last_published_time != null ? row.last_published_time : '-'
|
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: {
|
count: {
|
||||||
width: '80px',
|
width: '80px',
|
||||||
label: `${this.$t('ops.success')}/${this.$t('ops.total')}`,
|
label: `${this.$t('ops.success')}/${this.$t('ops.total')}`,
|
||||||
|
Reference in New Issue
Block a user