diff options
| author | zyh <[email protected]> | 2024-09-05 16:18:35 +0800 |
|---|---|---|
| committer | zyh <[email protected]> | 2024-09-05 16:18:35 +0800 |
| commit | 6cfa2da7fe543912a5d6d18152e9aa08448b1baa (patch) | |
| tree | 2172ce8de5c380d1aec523004d94ff8d138f1e7d /src | |
| parent | 2d0b749c42abdd40de929cde5f9e4e239abfe378 (diff) | |
ASW-55 feat: Device 远程调试界面开发
Diffstat (limited to 'src')
| -rw-r--r-- | src/axios/api/application.js | 4 | ||||
| -rw-r--r-- | src/axios/api/device.js | 90 | ||||
| -rw-r--r-- | src/axios/api/pcap.js | 2 | ||||
| -rw-r--r-- | src/components/attachmentUpload/index.vue | 19 | ||||
| -rw-r--r-- | src/i18n/en.js | 4 | ||||
| -rw-r--r-- | src/i18n/zh.js | 4 | ||||
| -rw-r--r-- | src/router/permissions.js | 6 | ||||
| -rw-r--r-- | src/styles/common.scss | 4 | ||||
| -rw-r--r-- | src/utils/index.js | 95 | ||||
| -rw-r--r-- | src/views/applications/history.vue | 2 | ||||
| -rw-r--r-- | src/views/applications/index.vue | 2 | ||||
| -rw-r--r-- | src/views/devices/index.vue | 35 | ||||
| -rw-r--r-- | src/views/devices/start.vue | 461 | ||||
| -rw-r--r-- | src/views/jobs/index.vue | 3 | ||||
| -rw-r--r-- | src/views/pcaps/index.vue | 29 |
15 files changed, 560 insertions, 200 deletions
diff --git a/src/axios/api/application.js b/src/axios/api/application.js index a7e8b8d..ccb5744 100644 --- a/src/axios/api/application.js +++ b/src/axios/api/application.js @@ -125,9 +125,9 @@ export const attachmentDownloadApi = async ( method: 'GET', ...config, }); - return res.data; + return res; } catch (err) { - return err.data; + return err; } }; diff --git a/src/axios/api/device.js b/src/axios/api/device.js index 67ee8e5..c562482 100644 --- a/src/axios/api/device.js +++ b/src/axios/api/device.js @@ -55,6 +55,20 @@ export const deviceSessionApi = async (id, data) => { } }; +// 结束会话 +export const deviceEndSessionApi = async (id, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${id}/session${sessionId}`, + method: 'DELETE', + data: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + // app列表 export const deviceAppListApi = async (envId, sessionId, data) => { try { @@ -70,7 +84,7 @@ export const deviceAppListApi = async (envId, sessionId, data) => { }; // 安装app -export const deviceInstallApi = async (data) => { +export const deviceInstallApi = async (envId, sessionId, data) => { try { const res = await axiosInstance({ url: `/api/v1/env/${envId}/session/${sessionId}/app`, @@ -85,7 +99,7 @@ export const deviceInstallApi = async (data) => { }; // 卸载app -export const deviceUninstallApi = async (data) => { +export const deviceUninstallApi = async (envId, sessionId, data) => { try { const res = await axiosInstance({ url: `/api/v1/env/${envId}/session/${sessionId}/app`, @@ -97,3 +111,75 @@ export const deviceUninstallApi = async (data) => { return err.data; } }; + +// 捕包 +export const deviceTcpdumpApi = async (envId, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `api/v1/env/${envId}/session/${sessionId}/pcap`, + method: 'POST', + data: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + +// 停止捕包 +export const deviceStopTcpdumpApi = async (envId, sessionId, data, config) => { + try { + const res = await axiosInstance({ + url: `api/v1/env/${envId}/session/${sessionId}/pcap`, + method: 'DELETE', + params: data, + ...config, + }); + return res; + } catch (err) { + return err; + } +}; + +// 文件列表 +export const deviceFileListApi = async (envId, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/file`, + method: 'GET', + params: data, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + +// 文件上传 +export const deviceFileUploadApi = async (envId, sessionId, data) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/file`, + method: 'POST', + data: data, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + return res.data; + } catch (err) { + return err.data; + } +}; + +// 文件下载 +export const deviceFileDownloadApi = async (envId, sessionId, data, config) => { + try { + const res = await axiosInstance({ + url: `/api/v1/env/${envId}/session/${sessionId}/file/${data.fileId}`, + method: 'GET', + ...config, + }); + return res; + } catch (err) { + return err; + } +}; diff --git a/src/axios/api/pcap.js b/src/axios/api/pcap.js index 5c38742..946370f 100644 --- a/src/axios/api/pcap.js +++ b/src/axios/api/pcap.js @@ -40,7 +40,7 @@ export const pcapDownloadApi = async (data, config) => { }); return res; } catch (err) { - return err.data; + return err; } }; diff --git a/src/components/attachmentUpload/index.vue b/src/components/attachmentUpload/index.vue index ff21b0b..4c1ec54 100644 --- a/src/components/attachmentUpload/index.vue +++ b/src/components/attachmentUpload/index.vue @@ -80,7 +80,7 @@ import { attachmentDownloadApi, attachmentDeleteApi, } from '@/axios/api'; -import { getTimeAgo, getUUID, imageBaseUrl } from '@/utils/index'; +import { getTimeAgo, getUUID, imageBaseUrl, downloadFile } from '@/utils/index'; import { ElMessage, ElMessageBox } from 'element-plus'; const router = useRouter(); @@ -181,20 +181,7 @@ const download = async (data) => { const res = await attachmentDownloadApi(routeParams.id, data.id, { responseType: 'blob', }); - if (window.navigator.msSaveBlob) { - // 兼容ie11 - const blobObject = new Blob([res]); - window.navigator.msSaveBlob(blobObject, data.name); - } else { - const url = URL.createObjectURL(new Blob([res])); - const a = document.createElement('a'); - document.body.appendChild(a); // 此处增加了将创建的添加到body当中 - a.href = url; - a.download = data.name; - a.target = '_blank'; - a.click(); - a.remove(); // 将a标签移除 - } + downloadFile(res, data.name); }; // 图片预览 @@ -294,7 +281,7 @@ const handleLeave = (e) => { onMounted(() => { if (type.value == 'edit') { dragRef.value.addEventListener('drop', handleDrop); - dragRef.value.addEventListener('dragenter',handleEnter); + dragRef.value.addEventListener('dragenter', handleEnter); dragRef.value.addEventListener('dragover', handleEnter); dragRef.value.addEventListener('dragleave', handleLeave); } diff --git a/src/i18n/en.js b/src/i18n/en.js index 56ced4c..c658950 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -168,10 +168,14 @@ export default { input_package_name: 'Please input package name', capture: 'Capture', stop_capture: 'Stop capture', + stop_capture: 'Stop capture', file_explorer: 'File explorer', attribute: 'Attribute', owner: 'Owner', group: 'Group', + file: 'File', + catalogue_file: 'Catalogue file', + return_file: 'Whether to return the pcap file?', }, job: { create_job: 'Create Job', diff --git a/src/i18n/zh.js b/src/i18n/zh.js index a3eec5d..7b00c39 100644 --- a/src/i18n/zh.js +++ b/src/i18n/zh.js @@ -167,11 +167,15 @@ export default { confirm_uninstall: '是否卸载此包?', input_package_name: '请输入包名', capture: '捕包', + stop_capture: '停止 捕包', end_capture: '停止捕包', file_explorer: '文件浏览器', attribute: '属性', owner: '所有人', group: '组', + file: '文件', + catalogue_file: '目录文件', + return_file: '是否返回pcap文件?', }, job: { create_job: '创建职位', diff --git a/src/router/permissions.js b/src/router/permissions.js index 55aee0c..b1a1178 100644 --- a/src/router/permissions.js +++ b/src/router/permissions.js @@ -37,12 +37,12 @@ const systemRoutes = { component: () => import('@/views/applications/detail.vue'), }, { - path: '/:workspace?/application/:id/:from?/history', + path: '/:workspace?/application/:id/:from/history', name: 'application_history', component: () => import('@/views/applications/history.vue'), }, { - path: '/:workspace?/application/:id/:oldVersion/:newVersion/:from?/compare', + path: '/:workspace?/application/:id/:oldVersion/:newVersion/:from/compare', name: 'application_compare', component: () => import('@/views/applications/compare.vue'), }, @@ -72,7 +72,7 @@ const systemRoutes = { component: () => import('@/views/devices/index.vue'), }, { - path: '/:workspace?/:id/start', + path: '/:workspace?/:id/:sessionId/start', name: 'device_start', component: () => import('@/views/devices/start.vue'), }, diff --git a/src/styles/common.scss b/src/styles/common.scss index 492fd37..249bec1 100644 --- a/src/styles/common.scss +++ b/src/styles/common.scss @@ -56,6 +56,10 @@ background-color: var(--el-dropdown-menuItem-hover-fill); color: var(--el-dropdown-menuItem-hover-color); } +.el-upload-dragger.is-dragover{ + padding: var(--el-upload-dragger-padding-horizontal) var(--el-upload-dragger-padding-vertical); + border: 1px dashed var(--el-color-primary); +} .slide-fade-enter-active { transition: transform 0.3s ease-out; diff --git a/src/utils/index.js b/src/utils/index.js index e8d0427..4b2513e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -4,6 +4,7 @@ import { cloneDeep, get } from 'lodash'; import { useI18n } from 'vue-i18n'; import axiosInstance from '@/axios/index.js'; import { administrator } from '@/utils/constants'; +import { ElMessage } from 'element-plus'; export * from './unit'; export * from './validator'; @@ -13,8 +14,8 @@ export * from './validator'; * @return { Boolean } */ export const isDev = () => { - return import.meta.env.DEV -} + return import.meta.env.DEV; +}; // 退出登录清除数据 export const logoutClear = () => { @@ -25,11 +26,11 @@ export const logoutClear = () => { // 跳转到首页 export const goHome = () => { const systemStore = useSystemStore(); - const menus = get(systemStore, 'menus.0.name', ''); + const routeName = get(systemStore, 'menus.0.name', ''); const workspace = get(systemStore, 'workspace.name', ''); router.push({ - name: menus, + name: routeName, params: { workspace: workspace, }, @@ -110,33 +111,87 @@ export const getTimeAgo = (timestamp1, timestamp2, user) => { } }; -export function getUUID () { - function S4 () { - return (((1 + window.crypto.getRandomValues(new Uint32Array(10))[0]) * 0x10000) | 0).toString(16).substring(1) +export function getUUID() { + function S4() { + return ( + ((1 + window.crypto.getRandomValues(new Uint32Array(10))[0]) * 0x10000) | + 0 + ) + .toString(16) + .substring(1); } - return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()) + return ( + S4() + + S4() + + '-' + + S4() + + '-' + + S4() + + '-' + + S4() + + '-' + + S4() + + S4() + + S4() + ); } // 上传图片不携带baseUrl 本地预览时显示 export const imageBaseUrl = (image) => { if (isDev() && image && image.startsWith('/')) { - let baseURL = axiosInstance.defaults.baseURL - baseURL=baseURL?.endsWith('/') ? baseURL.slice(0, -1) : baseURL - return baseURL && baseURL + image + let baseURL = axiosInstance.defaults.baseURL; + baseURL = baseURL?.endsWith('/') ? baseURL.slice(0, -1) : baseURL; + return baseURL && baseURL + image; } else { - return image + return image; + } +}; + +// 下载文件 +export function downloadFile(res, filename) { + if (res.status == 200) { + if (res.headers['content-disposition']) { + filename = res.headers['content-disposition'] + .split(';')[1] + .split('filename=')[1]; + } + + const blobObject = new Blob([res.data], { type: res.data.type }); + if (window.navigator.msSaveBlob) { + // 兼容ie11 + window.navigator.msSaveBlob(blobObject, filename); + } else { + const url = URL.createObjectURL(blobObject); + const a = document.createElement('a'); + document.body.appendChild(a); // 此处增加了将创建的添加到body当中 + a.href = url; + a.download = filename; + a.target = '_blank'; + a.click(); + a.remove(); // 将a标签移除 + } + } else { + const reader = new FileReader(); + reader.onload = function () { + const responseText = reader.result; + if (reader.result) { + const exception = JSON.parse(responseText); + ElMessage.error(exception.msg || exception.error); + } + }; + reader.readAsText(res.data); } } -export function isAdministrator () { - let isAdmin = false - let info = localStorage.getItem('asg-userInfo') +export function isAdministrator() { + let isAdmin = false; + let info = localStorage.getItem('asg-userInfo'); try { - let userInfo = JSON.parse(info) - if(userInfo.accessLevel === administrator) { - isAdmin = true + let userInfo = JSON.parse(info); + if (userInfo.accessLevel === administrator) { + isAdmin = true; } } catch (error) {} - return isAdmin -}
\ No newline at end of file + return isAdmin; +} diff --git a/src/views/applications/history.vue b/src/views/applications/history.vue index b471dc3..942fc12 100644 --- a/src/views/applications/history.vue +++ b/src/views/applications/history.vue @@ -71,7 +71,7 @@ </template> </el-table-column> <el-table-column - width="100" + width="105" fixed="right" align="center" :label="t('overall.actions')" diff --git a/src/views/applications/index.vue b/src/views/applications/index.vue index 117467a..3660250 100644 --- a/src/views/applications/index.vue +++ b/src/views/applications/index.vue @@ -73,7 +73,7 @@ </template> </el-table-column> <el-table-column - width="100" + width="105" fixed="right" align="center" :label="t('overall.actions')" diff --git a/src/views/devices/index.vue b/src/views/devices/index.vue index 4aff78a..c4d30ca 100644 --- a/src/views/devices/index.vue +++ b/src/views/devices/index.vue @@ -107,7 +107,7 @@ </template> </el-table-column> <el-table-column - width="100" + width="105" fixed="right" align="center" :label="t('overall.actions')" @@ -312,22 +312,23 @@ const fileSize = (number) => { // 启动 const start = async (id) => { - // const params = { - // workspace: workspace.value.id, - // }; - // const res = await deviceSessionApi(id, params); - // if (res.code == 200) { - // const routeData = router.resolve({ - // name: 'device_start', - // params: { - // workspace: workspace.value.name, - // id: id, - // }, - // }); - // window.open(routeData.href, '_blank'); - // } else { - // ElMessage.error(res.msg || res.error); - // } + const params = { + workspace: workspace.value.id, + }; + const res = await deviceSessionApi(id, params); + if (res.code == 200) { + const routeData = router.resolve({ + name: 'device_start', + params: { + workspace: workspace.value.name, + id: id, + sessionId: res.data.record.id, + }, + }); + window.open(routeData.href, '_blank'); + } else { + ElMessage.error(res.msg || res.error); + } }; const packets = (summary) => { diff --git a/src/views/devices/start.vue b/src/views/devices/start.vue index 3354603..92667ef 100644 --- a/src/views/devices/start.vue +++ b/src/views/devices/start.vue @@ -2,10 +2,10 @@ <div id="desktop"> <div class="desktop-left"> <div class="device-header"> - <span>Xiaomi12</span> + <span>{{ deviceInfo.name }}</span> <span> - <span>{{ t('device.location') }}:</span> - <span>Beijing XxG 3 floor</span> + <span>{{ t('device.location') }}: </span> + <span>{{ deviceInfo.location }}</span> </span> </div> <div ref="novncRef" class="device-novnc"></div> @@ -36,9 +36,9 @@ </el-upload> <div class="install-title">{{ t('device.installed') }}</div> - <ul class="install-list"> - <li class="install-item" v-for="item in installed" :key="item.id"> - <span>{{ item.name }}</span> + <ul class="install-list" v-loading="appLoading"> + <li class="install-item" v-for="item in appList" :key="item.id"> + <span>{{ item.packageName }}</span> <el-button type="primary" size="default" @@ -60,7 +60,15 @@ > </el-input> - <el-button type="primary" size="large" @click="capture"> + <el-button + v-if="captureId" + type="primary" + size="large" + @click="stopCapture" + > + {{ t('device.stop_capture') }} + </el-button> + <el-button v-else type="primary" size="large" @click="capture"> {{ t('device.capture') }} </el-button> </el-tab-pane> @@ -68,13 +76,30 @@ <el-tab-pane :label="t('device.file_explorer')" class="files-tab"> <div class="directory-header"> <div> - <i class="asw-icon icon-home"></i> + <i class="asw-icon icon-home" @click="getDirectory('/')"></i> + <span + v-for="(item, index) in directoryCrumb" + :key="index" + class="breadcrumb-item" + > + <i>/</i> + <span class="breadcrumb-name" @click="goPath(item, index)"> + {{ item }} + </span> + </span> </div> <div> - <i class="asw-icon icon-shangchuan" @click="uploadFile"></i> + <el-upload + action="" + :auto-upload="false" + :show-file-list="false" + :on-change="uploadFile" + > + <i class="asw-icon icon-shangchuan"></i> + </el-upload> </div> </div> - <div class="directory-content"> + <div class="directory-content" v-loading="directoryLoading"> <div class="directory-title"> <div class="directory-name">{{ t('overall.name') }}</div> <div class="directory-size">{{ t('overall.file_size') }}</div> @@ -82,19 +107,34 @@ <div class="directory-actions">{{ t('overall.actions') }}</div> </div> <el-scrollbar class="directory-list"> + <div v-if="directoryPath != '/'" class="directory-item"> + <div class="directory-name" @dblclick="backDirectory"> + <svg> + <use xlink:href="#icon-wenjianjia"></use> + </svg> + <span>..</span> + </div> + </div> <div class="directory-item" v-for="item in directoryData" :key="item.id" + @dblclick="selectFile(item)" > - <div class="directory-name">{{ item.name }}</div> + <div class="directory-name"> + <svg v-if="item.isDir"> + <use xlink:href="#icon-wenjianjia"></use> + </svg> + <svg v-else fill="var(--text_secondary)"> + <use xlink:href="#icon-wenben"></use> + </svg> + <span>{{ item.name }}</span> + </div> <div class="directory-size"> - {{ fileSize(item.size || 0) }} + <span v-if="!item.isDir">{{ fileSize(item.size || 0) }}</span> </div> <div class="directory-time"> - {{ - moment(item.createTimestamp).format('YYYY-MM-DD HH:mm:ss') - }} + {{ moment(item.cts * 1000).format('YYYY-MM-DD HH:mm:ss') }} </div> <div class="directory-actions"> <div class="flex-align-center" style="gap: 0 10px"> @@ -112,14 +152,17 @@ <i class="asw-icon icon-More1 cp"></i> <template #dropdown> <el-dropdown-menu> - <el-dropdown-item @click="directoryDownload(item)"> + <el-dropdown-item + :disabled="item.isDir" + @click="directoryDownload(item)" + > <i class="asw-icon icon-Download"></i> <span>{{ t('overall.download') }}</span> </el-dropdown-item> - <el-dropdown-item @click="directoryDelete(item)"> + <!-- <el-dropdown-item @click="directoryDelete(item)"> <i class="asw-icon icon-Delete"></i> <span>{{ t('overall.delete') }}</span> - </el-dropdown-item> + </el-dropdown-item> --> </el-dropdown-menu> </template> </el-dropdown> @@ -140,46 +183,61 @@ class="directory-dialog" > <div class="directory-dialog-name"> - <svg> + <svg v-if="directoryInfo.isDir"> <use xlink:href="#icon-wenjianjia"></use> </svg> - <!-- <svg fill="var(--text_secondary)"> + <svg v-else fill="var(--text_secondary)"> <use xlink:href="#icon-wenben"></use> - </svg> --> - <span>media </span> + </svg> + <span>{{ directoryInfo.name }} </span> </div> <div class="directory-dialog-info"> <div class="directory-info-row"> - <span>{{t('overall.type')}}:</span> - <span class="directory-info-row-value">Catalog File</span> + <span>{{ t('overall.type') }}:</span> + <span class="directory-info-row-value"> + <template v-if="directoryInfo.isDir"> + {{ t('device.catalogue_file') }} + </template> + <template v-else>{{ t('device.file') }}</template> + </span> </div> <div class="directory-info-row"> - <span>{{t('overall.ip')}}:</span> - <span class="directory-info-row-value">192.168.40.44</span> + <span>{{ t('overall.ip') }}:</span> + <span class="directory-info-row-value"> + {{ get(deviceInfo, 'param.url', '') }} + </span> </div> <div class="directory-info-row"> - <span>{{t('device.location')}}:</span> - <span class="directory-info-row-value">/Appsketch</span> + <span>{{ t('device.location') }}:</span> + <span class="directory-info-row-value">{{ directoryPath }}</span> </div> <div class="directory-info-row"> - <span>{{t('overall.size')}}:</span> - <span class="directory-info-row-value">21</span> + <span>{{ t('overall.size') }}:</span> + <span class="directory-info-row-value"> + <template v-if="!directoryInfo.isDir"> + {{ fileSize(directoryInfo.size || 0) }} + </template> + </span> </div> <div class="directory-info-row"> - <span>{{t('overall.modify_time')}}:</span> - <span class="directory-info-row-value">2023/10/27 16:40:00</span> + <span>{{ t('overall.modify_time') }}:</span> + <span class="directory-info-row-value"> + {{ moment(directoryInfo.uts * 1000).format('YYYY-MM-DD HH:mm:ss') }} + </span> </div> <div class="directory-info-row"> - <span>{{t('device.owner')}}:</span> - <span class="directory-info-row-value">1234</span> + <span>{{ t('device.owner') }}:</span> + <span class="directory-info-row-value">{{ directoryInfo.uid }} </span> </div> <div class="directory-info-row"> - <span>{{t('device.group')}}:</span> - <span class="directory-info-row-value">1234</span> + <span>{{ t('device.group') }}:</span> + <span class="directory-info-row-value">{{ directoryInfo.gid }} </span> </div> <div class="directory-info-row"> - <span>{{t('overall.permission')}}:</span> - <span class="directory-info-row-value">drwxr-xr-x</span> + <span>{{ t('overall.permission') }}:</span> + <span class="directory-info-row-value" + >{{ directoryInfo.permissions }} + </span> </div> </div> </el-dialog> @@ -187,20 +245,28 @@ <script setup> import { useRouter } from 'vue-router'; -import { ref, onMounted, onUnmounted } from 'vue'; +import { ref, onMounted, onUnmounted, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import RFB from '@novnc/novnc/lib/rfb'; import mitter from '@/utils/mitter'; import { debounce } from 'lodash'; -import { bytes } from '@/utils'; +import { bytes, downloadFile } from '@/utils'; import moment from 'moment-timezone'; +import { get } from 'lodash'; import { + deviceEndSessionApi, deviceDetailApi, deviceAppListApi, deviceInstallApi, deviceUninstallApi, + deviceTcpdumpApi, + deviceStopTcpdumpApi, + deviceFileUploadApi, + deviceFileListApi, + deviceFileDownloadApi, } from '@/axios/api'; import { ElMessage, ElMessageBox } from 'element-plus'; +import axiosInstance from '@/axios/index.js'; const router = useRouter(); const { t } = useI18n(); @@ -213,29 +279,37 @@ const disposeRoute = () => { disposeRoute(); // 已下载列表 -const installed = ref([ - { packageName: 1, name: 'com.bytedance.douyin' }, - { packageName: 2, name: 'com.bytedance.douyin' }, - { packageName: 3, name: 'com.bytedance.douyin' }, - { packageName: 4, name: 'com.bytedance.douyin' }, - { packageName: 5, name: 'com.bytedance.douyin' }, -]); +const appLoading = ref(false); +const appList = ref([]); //获取已下载列表 -const getinstalledList = async () => { - const res = await deviceAppListApi(); +const getAppList = async () => { + appLoading.value = true; + const params = { + arg: -3, + }; + const res = await deviceAppListApi( + routeParams.id, + routeParams.sessionId, + params + ); if (res.code == 200) { - installed.value = res.data.records; + appList.value = res.data.records; } else { ElMessage.error(res.msg || res.error); } + appLoading.value = false; }; -// 下载 -const install = debounce(async (file, fileList) => { +// 安装 +const install = debounce(async (file) => { let params = new FormData(); - params.append('file', file); - const res = await deviceInstallApi(params); + params.append('file', file.raw); + const res = await deviceInstallApi( + routeParams.id, + routeParams.sessionId, + params + ); if (res.code == 200) { - getinstalledList(); + getAppList(); ElMessage.success(t('message.save_success')); } else { ElMessage.error(res.msg || res.error); @@ -251,9 +325,13 @@ const uninstall = (packageName) => { const params = { packageName: packageName, }; - const res = await deviceUninstallApi(params); + const res = await deviceUninstallApi( + routeParams.id, + routeParams.sessionId, + params + ); if (res.code == 200) { - fetchList(); + getAppList(); ElMessage.success(t('message.save_success')); } else { ElMessage.error(res.msg || res.error); @@ -262,62 +340,196 @@ const uninstall = (packageName) => { }; const packageName = ref(''); +const captureId = ref(0); +const captureLoading = ref(false); // 捕包 -const capture = () => { - console.log(666); +const capture = async () => { + if (captureLoading.value) { + return; + } + captureLoading.value = true; + + const params = { + packageName: packageName.value, + }; + const res = await deviceTcpdumpApi( + routeParams.id, + routeParams.sessionId, + params + ); + if (res.code == 200) { + captureId.value = res.data.id; + } else { + ElMessage.error(res.msg || res.error); + } + + captureLoading.value = false; }; +// 停止捕包 +const stopCapture = async () => { + if (captureLoading.value) { + return; + } + captureLoading.value = true; -const directoryData = ref([ - { id: 1, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 2, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 3, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 4, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 5, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 6, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 7, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 8, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 9, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 10, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 11, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 12, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 13, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 14, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 15, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 16, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 17, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 18, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 19, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 20, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 21, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 22, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 23, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 24, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, - { id: 25, name: 'root', size: 30412456, createTimestamp: 1725345967289 }, -]); + const params = { id: captureId.value, returnFile: true }; + let config = {}; + try { + await ElMessageBox.confirm(t('device.return_file'), t('overall.hint'), { + confirmButtonText: t('overall.confirm'), + cancelButtonText: t('overall.cancel'), + type: 'warning', + }); -const fileSize = (number) => { - const data = bytes(number, -1, 1); - return data.value + ' ' + data.unit; + params.returnFile = true; + config = { + responseType: 'blob', + }; + } catch (error) { + params.returnFile = false; + } + + const res = await deviceStopTcpdumpApi( + routeParams.id, + routeParams.sessionId, + params, + config + ); + if (params.returnFile) { + downloadFile(res); + } else { + if (res.data.code == 200) { + } else { + ElMessage.error(res.data.msg || res.data.error); + } + } + captureId.value = 0; + captureLoading.value = false; +}; + +// 获取文件目录列表 +const directoryLoading = ref(false); +const directoryData = ref([]); +const directoryPath = ref('/'); +const getDirectory = async (path) => { + directoryLoading.value = true; + const params = { + path: path, + }; + const res = await deviceFileListApi( + routeParams.id, + routeParams.sessionId, + params + ); + if (res.code == 200) { + directoryData.value = res.data.records; + directoryPath.value = res.data.path; + } else { + ElMessage.error(res.msg || res.error); + } + directoryLoading.value = false; +}; + +const directoryCrumb = computed(() => { + return directoryPath.value.split('/').filter((item) => item); +}); + +const selectFile = (data) => { + if (!data.isDir) { + return false; + } + let path = directoryPath.value == '/' ? '' : directoryPath.value; + path += '/' + data.name; + getDirectory(path); +}; +const backDirectory = () => { + const path = '/' + directoryCrumb.value.slice(0, -1).join('/'); + getDirectory(path); +}; +const goPath = (item, index) => { + const path = '/' + directoryCrumb.value.slice(0, index + 1).join('/'); + getDirectory(path); +}; + +// 上传文件 +const uploadFile = async (file) => { + let params = new FormData(); + params.append('file', file.raw); + params.append('path', directoryPath.value); + const test = { + file: file.raw, + path: directoryPath.value, + }; + const res = await deviceFileUploadApi( + routeParams.id, + routeParams.sessionId, + params + ); + if (res.code == 200) { + getDirectory(directoryPath.value); + ElMessage.success(t('message.save_success')); + } else { + ElMessage.error(res.msg || res.error); + } }; +// 查看文件 const visible = ref(false); -const directoryView = async () => { +const directoryInfo = ref({}); +const directoryView = async (data) => { + directoryInfo.value = data; visible.value = true; }; -const directoryDownload = async () => {}; -const directoryDelete = async () => {}; +// 下载文件 +const directoryDownload = async (data) => { + let path = directoryPath.value == '/' ? '' : directoryPath.value; + path += '/' + data.name; + const params = { + fileId: data.id, + }; + const res = await deviceFileDownloadApi( + routeParams.id, + routeParams.sessionId, + params, + { responseType: 'blob' } + ); + downloadFile(res); +}; +// 删除文件 +const directoryDelete = async (data) => {}; + +const fileSize = (number) => { + const data = bytes(number, -1, 1); + return data.value + ' ' + data.unit; +}; +// 连接novnc const novncRef = ref(null); let rfb = null; const connect = () => { - const PASSWORD = ''; //VNC Server 密码 - const url = 'ws://192.168.45.60/api/v1/device/1/novnc'; - rfb = new RFB(novncRef.value, url, { - // 向vnc 传递的一些参数,比如说虚拟机的开机密码等 - credentials: { password: PASSWORD }, + let baseURL = axiosInstance.defaults.baseURL; + console.log(baseURL); + const protocol = + window.location.protocol.indexOf('https') > -1 ? 'wss' : 'ws'; + let url = ''; + if (baseURL.startsWith('/')) { + url = `${protocol}://` + window.location.host + baseURL; + } else { + url = baseURL.replace('http://', 'ws://').replace('https://', 'wss://'); + } + if (!url.endsWith('/')) { + url += '/'; + } + const token = get(deviceInfo, 'value.param.token', ''); + url += `api/v1/env/${routeParams.id}/session/${routeParams.sessionId}/novnc?token=${token}`; + + rfb = new RFB(novncRef.value, url); + rfb.addEventListener('connect', (e) => { + // console.log(e); + }); + rfb.addEventListener('disconnect', (e) => { + // console.log(e); }); - rfb.addEventListener('connect', (e) => {}); - rfb.addEventListener('disconnect', (e) => {}); rfb.scaleViewport = false; // 远程会话是否应在本地缩放以适合其容器 rfb.resizeSession = true; // 每当容器尺寸调整是否发送调整远程会话大小的请求 rfb.clipViewport = true; // 远程会话是否应被剪裁到其容器,禁用时将显示滚动条以处理由此产生的溢出 @@ -325,18 +537,19 @@ const connect = () => { rfb.viewOnly = false; // 是否应阻止将任何事件(例如按键或鼠标移动)发送到服务器 }; +// 获取设备信息 const loading = ref(false); +const deviceInfo = ref({}); const getData = async () => { loading.value = true; const res = await deviceDetailApi(routeParams.id); if (res.code == 200) { - console.log(res); + deviceInfo.value = res.data.record; } else { ElMessage.error(res.msg || res.error); } loading.value = false; }; -getData() const deviceBack = (e) => { rfb.sendKey('0xff1b', 'Escape', true); @@ -357,13 +570,20 @@ const destroy = () => { mitter.off('desktopEnd'); rfb.disconnect(); }; -mitter.on('desktopEnd', () => { - destroy(); - window.close(); +// 结束会话 +mitter.on('desktopEnd', async () => { + const res = await deviceEndSessionApi(routeParams.id, routeParams.sessionId); + if (res.code == 200) { + destroy(); + window.close(); + } else { + ElMessage.error(res.msg || res.error); + } }); - -onMounted(() => { - getinstalledList(); +onMounted(async () => { + getAppList(); + getDirectory(directoryPath.value); + await getData(); connect(); }); onUnmounted(() => { @@ -509,7 +729,8 @@ onUnmounted(() => { display: flex; align-items: center; justify-content: space-between; - i { + i.icon-home, + i.icon-shangchuan { font-size: 16px; color: var(--text_secondary); cursor: pointer; @@ -519,12 +740,28 @@ onUnmounted(() => { } i.icon-home { color: var(--text); + margin-right: 4px; + } + .breadcrumb-item { + font-size: 14px; + color: var(--text_secondary); + i { + font-style: normal; + margin: 0 2px; + } + .breadcrumb-name { + cursor: pointer; + &:hover { + color: var(--primary); + } + } } } .directory-content { height: calc(100% - 54px); padding: 20px; padding-top: 0; + user-select: none; .directory-title { height: 36px; line-height: 36px; @@ -557,6 +794,12 @@ onUnmounted(() => { } .directory-name { width: 40%; + svg { + width: 16px; + height: 16px; + vertical-align: -0.15em; + margin-right: 6px; + } } .directory-size { width: 15%; @@ -599,7 +842,7 @@ onUnmounted(() => { height: 42px; display: flex; align-items: center; - gap: 0 8px; + gap: 0 6px; border-bottom: 1px solid var(--border); svg { width: 16px; diff --git a/src/views/jobs/index.vue b/src/views/jobs/index.vue index 75aea31..aaa1557 100644 --- a/src/views/jobs/index.vue +++ b/src/views/jobs/index.vue @@ -89,7 +89,7 @@ </template> </el-table-column> <el-table-column - width="100" + width="105" fixed="right" align="center" :label="t('overall.actions')" @@ -121,7 +121,6 @@ import { toThousands } from '@/utils'; const { t } = useI18n(); const tableView = (row) => { - console.log(row); }; const tableTitle = [ diff --git a/src/views/pcaps/index.vue b/src/views/pcaps/index.vue index 3413fc2..a14c1ae 100644 --- a/src/views/pcaps/index.vue +++ b/src/views/pcaps/index.vue @@ -154,7 +154,7 @@ </template> </el-table-column> <el-table-column - width="100" + width="105" fixed="right" align="center" :label="t('overall.actions')" @@ -186,7 +186,6 @@ import { onBeforeMount, computed, watch, - onActivated, } from 'vue'; import { useI18n } from 'vue-i18n'; import { @@ -201,7 +200,7 @@ import { } from '@/axios/api'; import moment from 'moment-timezone'; import { ElMessage, ElMessageBox } from 'element-plus'; -import { toThousands, bytes, hash } from '@/utils'; +import { toThousands, bytes, hash, downloadFile } from '@/utils'; import { debounce } from 'lodash'; import { useSystemStore } from '@/store/index'; import axios from 'axios'; @@ -330,24 +329,7 @@ const download = async () => { const res = await pcapDownloadApi(params, { responseType: 'blob', }); - - const filename = res.headers['content-disposition'] - .split(';')[1] - .split('filename=')[1]; - if (window.navigator.msSaveBlob) { - // 兼容ie11 - const blobObject = new Blob([res.data]); - window.navigator.msSaveBlob(blobObject, filename); - } else { - const url = URL.createObjectURL(new Blob([res.data])); - const a = document.createElement('a'); - document.body.appendChild(a); // 此处增加了将创建的添加到body当中 - a.href = url; - a.download = filename; - a.target = '_blank'; - a.click(); - a.remove(); // 将a标签移除 - } + downloadFile(res); }; // 上传数据 @@ -472,7 +454,6 @@ const { orderBy, sortChange } = useTable('-create_timestamp', fetchList); // 查看详情 const tableView = (row) => { - console.log(row); }; // 删除 @@ -499,10 +480,6 @@ onBeforeMount(() => { fetchList(); }); -onActivated(() => { - console.log(666); -}); - const fileSize = (number) => { const data = bytes(number, -1, 3); return data.value + ' ' + data.unit; |
