summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzyh <[email protected]>2024-09-05 16:18:35 +0800
committerzyh <[email protected]>2024-09-05 16:18:35 +0800
commit6cfa2da7fe543912a5d6d18152e9aa08448b1baa (patch)
tree2172ce8de5c380d1aec523004d94ff8d138f1e7d /src
parent2d0b749c42abdd40de929cde5f9e4e239abfe378 (diff)
ASW-55 feat: Device 远程调试界面开发
Diffstat (limited to 'src')
-rw-r--r--src/axios/api/application.js4
-rw-r--r--src/axios/api/device.js90
-rw-r--r--src/axios/api/pcap.js2
-rw-r--r--src/components/attachmentUpload/index.vue19
-rw-r--r--src/i18n/en.js4
-rw-r--r--src/i18n/zh.js4
-rw-r--r--src/router/permissions.js6
-rw-r--r--src/styles/common.scss4
-rw-r--r--src/utils/index.js95
-rw-r--r--src/views/applications/history.vue2
-rw-r--r--src/views/applications/index.vue2
-rw-r--r--src/views/devices/index.vue35
-rw-r--r--src/views/devices/start.vue461
-rw-r--r--src/views/jobs/index.vue3
-rw-r--r--src/views/pcaps/index.vue29
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;