Initial commit

This commit is contained in:
ll
2024-07-11 20:41:37 +08:00
parent ac8bc7f895
commit bafa736c04
80 changed files with 11535 additions and 14057 deletions

23
.gitignore vendored
View File

@@ -1,23 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json

10988
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,16 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 兼容旧版IE浏览器 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 显示窗口的设置 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>DNS</title>
<script type="text/javascript">
window.g = {
// baseURL:'172.16.0.120:31540', //开发环境
baseURL: '124.221.228.62:2525', //正式环境
// baseURL: '124.221.228.62:2525', //正式环境
baseURL: '124.221.228.62:12526', //正式环境
// queryTags:[],
// switchLanguage:"en",
switchLanguage:"zh"

View File

@@ -13,6 +13,7 @@ export default {
//目标信息获取
targetQueryList:'/target/',
targetMap:'target/map',
targetNodes: 'target/nodes',
//目标信息筛选基础数据
targetFilter:'/target/filter',
//目标信息时延

1
src/api/world_v2.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -33,6 +33,7 @@ export default {
if (!appRef) return
// 当前宽高比
const currentRate = parseFloat((window.innerWidth / window.innerHeight).toFixed(5))
console.log("currentSize: ", window.innerWidth, "++++", window.innerHeight)
if (appRef) {
if (currentRate > baseProportion) {
// 表示更宽

BIN
src/img/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/img/logo_new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

BIN
src/img/logo_new_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

View File

@@ -3,16 +3,17 @@
<div class="headNew" >
<div class="headNewLeft" >
<div class="headNewLeftText">
<!-- <span>YYDNS</span>-->
<!-- <img src="../img/logyy.jpg">-->
<img src="../img/logyy_美图抠图20240424.png" style="width: 180px">
<!-- YYDNS图标 -->
<!-- <img src="../img/logo2.png" style="width: 300px;margin-top:1%"> -->
<!-- 信工所 + DNS 图标 -->
<img src="../img/logo_new_2.png" style="width: 300px;margin-top:6%;margin-left:10%">
</div>
</div>
<div class="headNewCenter" >
<!-- <div class="headNewCenter" >
<div class="headNewCenterText">
<span style="color: rgb(0,0,0,0);">{{ title || 'Dashboard' }}</span>
</div>
</div>
</div> -->
<div class="headNewRight" >
<div class="headNewRightAll" >
<img src="../img/user.png" style="margin-top: 0.5%;height: 10%;width: 10%">
@@ -32,9 +33,6 @@
export default {
name: "AppHeader",
props:['title','fTag','fInput','fFrom','FIsConfigQuery'],
// components:{
// router
// },
data(){
return{
userName: localStorage.getItem('username'),
@@ -81,12 +79,6 @@
rules:{
},
};
},
mounted() {
},
watch: {
},
created() {
if (sessionStorage.getItem('switchLanguage') !== null) {
@@ -109,7 +101,6 @@
// this.getallLanguage()
// this.getallTags()
// this.getallWebs()
},
methods:{
logOut() {
@@ -119,7 +110,6 @@
this.$router.push({path:'/'})
},
ChangeLanguage(){
if(this.switchLanguage==='en'){
// console.log("当前为英文页面 要换成中文页面")
sessionStorage.setItem("switchLanguage","zh");
@@ -338,7 +328,8 @@
/*margin-top: 3.1%;*/
margin-top: -8%;
/*margin-left: 35%;*/
margin-left: 10%;
margin-left: 5%;
float: left
}
}
.headNewCenter{
@@ -351,7 +342,6 @@
font-size: 25px;
margin-top: 1.6%;
}
}
.headNewRight{
position: absolute;

View File

@@ -3,11 +3,13 @@ import App from './App.vue'
import router from './router'
import store from './store'
import './assets/css/iconfont/iconfont.js'
import ElementUI from 'element-ui';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import * as echarts from 'echarts';
import { Message, Notification } from 'element-ui';
// // 引入 Echarts 和世界地图数据
import 'echarts/map/js/world.js'
Vue.use(ElementUI);
import './assets/css/iconfont/iconfont.css'

View File

@@ -35,7 +35,7 @@
bing: {
deep: true,
handler(val) {
console.log(val)
console.log("val: " + val)
this.bingfn(val)
}
},
@@ -63,208 +63,209 @@
myChart1.off('mouseover')
var option = {
animation:false, //动态展示
grid: {
left: '5%',
right: '5%',
bottom: '5%',
containLabel: true
},
// title: {
// text:"898989"
// },
baseOption: {
timeline: {
axisType: 'category', // 轴的类型
show: true, // 显示组件
autoPlay: true, // 自动播放
playInterval: 5000, // 播放间隔
data: ['IPv6', 'DNSSEC', 'DoT', 'DoH'],
top:'3%', // timeline组件离容器上侧的距离
left:'30%',
right:'30%',
symbol: 'none', // 设置标记的图形
lineStyle: {
show: false // 不显示轴线
},
controlStyle: {
show: false
},
label: {
fontSize: 15,
fontWeight: 'bold',
color: '#DCDCDC'
},
checkpointStyle: { // 『当前项』checkpoint的图形样式
symbol: 'none',
},
progress: {
label: {
fontSize: 20,
fontWeight: 'bolder',
color: '#F5F5F5'
}
xAxis: {
type: 'category',
data: data.xdata,
// data: ["2024-1-1","2024-1-2","2024-1-3","2024-1-4","2024-1-5","2024-1-6","2024-1-7"],
axisLine:{
lineStyle:{
color:'#f6fbfd'
},
emphasis: {
label: {
fontSize: 20,
}
}
},
axisLabel:{
interval:0,
show: true
}
},
// tooltip: {
// trigger: 'axis',
// // 添加样式
// textStyle: {
// textAlign: 'left' // 内容左对齐
// }
// },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
grid: {
left: '5%',
right: '5%',
bottom: '5%',
containLabel: true
},
// 使用 formatter 函数设置内容和样式
formatter: function(params) {
var result = '<div style="text-align:left;">'; // 左对齐样式
result += params[0].name + '<br>'; // 添加横坐标名称
params.forEach(function(item) {
result += item.marker + ' ' + item.seriesName + ': ' + item.value + '<br>'; // 添加数据项
});
result += '</div>';
return result;
}
},
yAxis: [
{
type: 'value',
name: '分类统计(个)', // 水量单位
position: 'left', // 显示在左侧
splitLine: {
show: false,
lineStyle: {
color: "rgb(255,16,18)",
xAxis: [
{
type: 'category',
data: data.xdata,
// data: ["2024-1-1","2024-1-2","2024-1-3","2024-1-4","2024-1-5","2024-1-6","2024-1-7"],
axisLine:{
lineStyle:{
color:'#f6fbfd'
}
},
axisLabel:{
interval:0,
show: true
}
},
axisLine: {
lineStyle: {
color: '#f4f2fd'
}
}
},
{
type: 'value',
name: '总量统计(个)', // 数量单位
position: 'right', // 显示在右侧
splitLine: {
show: false,
lineStyle: {
color: "rgb(255,16,18)",
],
yAxis: [
{
type: 'value',
name: '数量统计(个)', // 水量单位
position: 'left', // 显示在左侧
splitLine: {
show: false,
lineStyle: {
color: "rgb(255,16,18)",
}
},
axisLine: {
lineStyle: {
color: '#f4f2fd'
}
}
},
axisLine: {
lineStyle: {
color: '#f4be6a'
}
}
}
],
// yAxis: {
// type: 'value',
// splitLine: {//分割线配置
// show:false,
// lineStyle: {
// color: "rgb(255,16,18)",
// }
// },
// axisLine:{
// lineStyle:{
// color:'#fdc909'
// }
// }
// },
// yAxis:[
// {
// type:'value',
// name:'分类数量',
// interval: 5,
// axisLabel: {
// formatter:'{value}个'
// }
// },
// {
// type:'value',
// name:'总量',
// interval: 5,
// axisLabel: {
// formatter:'{value}个'
// }
// },
//
// ],
series: [
{
name:'IPv6',
barWidth: 20, //柱子宽度
data:data.ydata1,
// data:[45,89,56,58,66,12,96],
color:["#2386bf"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
// {
// type: 'value',
// name: '总量统计(个)', // 数量单位
// position: 'right', // 显示在右侧
// splitLine: {
// show: false,
// lineStyle: {
// color: "rgb(255,16,18)",
// }
// },
// axisLine: {
// lineStyle: {
// color: '#f4be6a'
// }
// }
// }
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
// 使用 formatter 函数设置内容和样式
formatter: function(params) {
var result = '<div style="text-align:left;">'; // 左对齐样式
result += params[0].name + '<br>'; // 添加横坐标名称
params.forEach(function(item) {
result += item.marker + ' ' + item.seriesName + ': ' + item.value + '<br>'; // 添加数据项
});
result += '</div>';
return result;
}
},
},
options: [
// IPv6
{
name:'DNS',
barWidth: 20, //柱子宽度
data:data.ydata2,
// data:[450,890,560,580,660,155,85],
color:["#434ff4"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
series: [
{
name:'IPv6',
barWidth: 45, //柱子宽度
data:data.ydata1,
// data:[45,89,56,58,66,12,96],
color:["#2386bf"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
},
],
},
// DNS
{
name:'DoT',
barWidth: 20, //柱子宽度
data:data.ydata3,
// data:[150,290,560,380,660,105,815],
color:["rgba(58,76,222,0.89)"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
series: [
{
name:'DNS',
barWidth: 45, //柱子宽度
data:data.ydata2,
// data:[450,890,560,580,660,155,85],
color:["#434ff4"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
},
]
},
// DoT
{
name:'DoH',
barWidth: 20, //柱子宽度
data:data.ydata4,
// data:[650,790,560,180,660,15,85],
color:["#117cf4"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
}
,
series: [
{
name:'DoT',
barWidth: 45, //柱子宽度
data:data.ydata3,
// data:[150,290,560,380,660,105,815],
color:["rgba(58,76,222,0.89)"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
},
]
},
// DoH
{
name:'总量',
barWidth: 20, //柱子宽度
data:data.ydata5,
// data:[1120,7090,5660,1800,6060,105,805],
color:["#f4be6a"], //自定义颜色
type: 'line',
yAxisIndex: 1,
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
series: [
{
name:'DoH',
barWidth: 45, //柱子宽度
data:data.ydata4,
// data:[650,790,560,180,660,15,85],
color:["#117cf4"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
},
]
}
],
]
//#region
/*
graphic: [
{
type:'group',
@@ -388,14 +389,11 @@
}
]
*/
//#endregion
};
myChart1.setOption(option,true)
window.addEventListener("resize", () => {
myChart1.resize();

View File

@@ -23,6 +23,7 @@ export default {
components: { TargetView, ImageView, SourceView, NodeView }
}
</script>
<style lang="less" scoped>
.home {
width: 100%;

View File

@@ -1,11 +1,13 @@
<template>
<div>
<div class="login" ref="appRef" v-if="label==false">
<div class="login-box">
<p class="title" style="font-size: 48px;margin-top: 20%" @click="label=!label">进入YYDNS系统</p>
</div>
</div>
<div class="login" ref="appRef" v-if="label==true">
<!-- 点击YYDNS前 -->
<div class="login" ref="appRef" v-if="label==false">
<div class="login-box">
<p class="title" style="font-size: 48px;margin-top: 20%" @click="label=!label">进入YYDNS系统</p>
</div>
</div>
<!-- 点击YYDNS后 -->
<div class="login" ref="appRef" v-if="label==true">
<div class="login-box">
<p class="title">登录到</p>
<p class="title">YYDNS系统</p>
@@ -38,7 +40,7 @@
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,75 @@
<template>
<div style="width: 100%;height: 100%;">
<div style="width: 100%;height: 10%"><span style="margin-top: 1%;font-size: 20px;margin-left: 2%;float: left">节点分布</span></div>
<div ref="worldmap" style="width: 100%;height: 90%"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
const worldJson = require("../../../api/world_v2.json")
export default {
name: "WorldMap",
props: {
nodes:{
type: Array,
},
},
watch: {
nodes: {
deep: true,
handler(val) {
console.log(val)
this.drawMap(val)
}
}
},
methods: {
// 调整节点信息格式
getInfo(){
return this.nodes.map(node => {
return [node.Lng, node.Lat]
})
},
// 绘制世界地图
drawMap(){
// 初始化myChart
var myChart = echarts.init(this.$refs.worldmap)
// 注册可用的地图
echarts.registerMap('world', worldJson)
var option = {
tooltip: {
show: true
},
geo: {
tooltip: {
show: true
},
map: 'world', // 使用registerMap注册的地图名称
roam: true, // 开启鼠标缩放和平移漫游
},
series: {
type: 'effectScatter',
coordinateSystem: 'geo',
itemStyle: {
color: '#b02a02'
},
encode: {
tooltip: 2
},
data: this.getInfo()
}
}
myChart.setOption(option)
},
},
mounted(){
this.drawMap()
},
}
</script>
<style scoped="scoped">
</style>

View File

@@ -62,6 +62,100 @@
myChart1.clear()
myChart1.off('mouseover')
var option = {
// dataset: {
// source: data.xdata
// },
grid: {
left: '5%',
right: '5%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.xdata,
// data: ["2024-1-1","2024-1-2","2024-1-3","2024-1-4","2024-1-5","2024-1-6","2024-1-7"],
axisLine:{
lineStyle:{
color:'#f6fbfd'
}
},
axisLabel:{
interval:0,
show: true
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
// 使用 formatter 函数设置内容和样式
formatter: function(params) {
var result = '<div style="text-align:left;">'; // 左对齐样式
result += params[0].name + '<br>'; // 添加横坐标名称
params.forEach(function(item) {
result += item.marker + ' ' + item.seriesName + ': ' + item.value + '<br>'; // 添加数据项
});
result += '</div>';
return result;
}
},
yAxis: [
{
type: 'value',
name: '时延(ms)',
position: 'left', // 显示在左侧
splitLine: {
show: false,
lineStyle: {
color: "rgb(255,16,18)",
}
},
axisLine: {
lineStyle: {
color: '#f4f2fd'
}
}
},
],
visualMap: {
orient: 'horizontal', // 组件布局方向
top: '1%',
right: '5%', // 组件位置
min:0,
max:800, // 映射数据范围
// text: ['High Score', 'Low Score'],
dimension: 1, // 映射数据的维度
inRange: {
color: ['#11da11', '#E15457']
}
},
series: [
{
name:'时延',
barWidth: 20, //柱子宽度
data:data.ydata1,
// data:[45,89,56,58,66,12,96],
color:["#2386bf"], //自定义颜色
type: 'bar',
label:{
show:true,
position:'top',
formatter:function (params) {
return params.value;
}
}
},
],
}
/*
var option = {
animation:false, //动态展示
grid: {
@@ -87,6 +181,7 @@
interval:0,
show: true
}
},
tooltip: {
@@ -267,6 +362,7 @@
//
// ]
};
*/

View File

@@ -1,419 +0,0 @@
<template>
<div style="width: 100%;height: 100%;">
<div style="width: 100%;height: 10%"><span style="margin-top: 1%;font-size: 20px;margin-left: 2%;float: left">时延报警</span></div>
<div ref="bingbox" style="width: 100%;height: 90%"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
var myChart1=null
export default {
props:{
bing:{
type:Object,
default: function() {
return {
data:[],
// signdata: [],
// title:"",
// bingtype:'',
// AreaName:[],
}
}
},
active:{
type:Number,
default: function() {
return {
active:""
}
}
}
},
watch: {
bing: {
deep: true,
handler(val) {
console.log(val)
this.bingfn(val)
}
},
},
data(){
return {
}
},
methods:{
bingfn(data){
console.log(data)
let that = this
//当前视口宽度
let nowClientWidth = document.documentElement.clientWidth;
// 换算方法
let nowSize = function (val, initWidth = 1920) {
return val * (nowClientWidth / initWidth);
};
if (myChart1 != null) {
myChart1.clear()//销毁
}
myChart1= echarts.init(this.$refs.bingbox)
myChart1.clear()
myChart1.off('mouseover')
// var option = {
// animation:false, //动态展示
// grid: {
// left: '5%',
// right: '5%',
// bottom: '5%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// data: data.xdata,
// // data: ["2024-1-1","2024-1-2","2024-1-3","2024-1-4","2024-1-5","2024-1-6","2024-1-7"],
// axisLine:{
// lineStyle:{
// color:'#f6fbfd'
// }
// },
// axisLabel:{
// interval:0,
// show: true
// }
//
// },
// tooltip: {
// trigger: 'axis',
// axisPointer: {
// type: 'shadow'
// },
// // 使用 formatter 函数设置内容和样式
// formatter: function(params) {
// var result = '<div style="text-align:left;">'; // 左对齐样式
// result += params[0].name + '<br>'; // 添加横坐标名称
//
// params.forEach(function(item) {
// result += item.marker + ' ' + item.seriesName + ': ' + item.value + '<br>'; // 添加数据项
// });
//
// result += '</div>';
// return result;
// }
// },
// yAxis: [
// {
// type: 'value',
// name: '分类统计(个)', // 水量单位
// position: 'left', // 显示在左侧
// splitLine: {
// show: false,
// lineStyle: {
// color: "rgb(255,16,18)",
// }
// },
// axisLine: {
// lineStyle: {
// color: '#f4f2fd'
// }
// }
// },
// {
// type: 'value',
// name: '总量统计(个)', // 数量单位
// position: 'right', // 显示在右侧
// splitLine: {
// show: false,
// lineStyle: {
// color: "rgb(255,16,18)",
// }
// },
// axisLine: {
// lineStyle: {
// color: '#f4be6a'
// }
// }
// }
// ],
// series: [
// {
// name:'IPv6',
// barWidth: 20, //柱子宽度
// data:data.ydata1,
// // data:[45,89,56,58,66,12,96],
// color:["#2386bf"], //自定义颜色
// type: 'bar',
// label:{
// show:true,
// position:'top',
// formatter:function (params) {
// return params.value;
// }
// }
//
// },
// {
// name:'DNS',
// barWidth: 20, //柱子宽度
// data:data.ydata2,
// // data:[450,890,560,580,660,155,85],
// color:["#434ff4"], //自定义颜色
// type: 'bar',
// label:{
// show:true,
// position:'top',
// formatter:function (params) {
// return params.value;
// }
// }
//
// },
// {
// name:'DoT',
// barWidth: 20, //柱子宽度
// data:data.ydata3,
// // data:[150,290,560,380,660,105,815],
// color:["rgba(58,76,222,0.89)"], //自定义颜色
// type: 'bar',
// label:{
// show:true,
// position:'top',
// formatter:function (params) {
// return params.value;
// }
// }
//
// },
// {
// name:'DoH',
// barWidth: 20, //柱子宽度
// data:data.ydata4,
// // data:[650,790,560,180,660,15,85],
// color:["#117cf4"], //自定义颜色
// type: 'bar',
// label:{
// show:true,
// position:'top',
// formatter:function (params) {
// return params.value;
// }
// }
//
// }
// ,
// {
// name:'总量',
// barWidth: 20, //柱子宽度
// data:data.ydata5,
// // data:[1120,7090,5660,1800,6060,105,805],
// color:["#f4be6a"], //自定义颜色
// type: 'line',
// yAxisIndex: 1,
// label:{
// show:true,
// position:'top',
// formatter:function (params) {
// return params.value;
// }
// }
//
// }
// ],
// graphic: [
// {
// type:'group',
// left:'center',
// top:25,
// children:[
// {
// type: 'rect',
// shape: {
// width: 20,
// height: 20
// },
// style: {
// fill: '#2386bf' // 设置第一个色块的颜色
// },
// z: 100
// },
// {
// type: 'text',
// left: 25,
// style: {
// text: 'IPv6', // 设置第一个色块的含义
// fill: '#2386bf',
// fontSize: 12
// },
// z: 100
// },
// ////////////////
// {
// type: 'rect',
// left: 75,
// shape: {
// width: 20,
// height: 20
// },
// style: {
// fill: '#434ff4' // 设置第二个色块的颜色
// },
// z: 100
// },
// {
// type: 'text',
// left: 100,
// style: {
// text: 'DNS', // 设置第二个色块的含义
// fill: '#434ff4',
// fontSize: 12
// },
// z: 100
// },
// ///////////////
// {
// type: 'rect',
// left:150 ,
// shape: {
// width: 20,
// height: 20
// },
// style: {
// fill: 'rgba(58,76,222,0.89)' // 设置第二个色块的颜色
// },
// z: 100
// },
// {
// type: 'text',
// left: 175,
// style: {
// text: 'DoT', // 设置第二个色块的含义
// fill: 'rgba(58,76,222,0.89)',
// fontSize: 12
// },
// z: 100
// },
// /////////////////////////////////
// {
// type: 'rect',
// left: 225,
// shape: {
// width: 20,
// height: 20
// },
// style: {
// fill: '#117cf4' // 设置第二个色块的颜色
// },
// z: 100
// },
// {
// type: 'text',
// left: 250,
// style: {
// text: 'DoH', // 设置第二个色块的含义
// fill: '#117cf4',
// fontSize: 12
// },
// z: 100
// },
// // //////////////////////////////
// // {
// // type: 'rect',
// // left: 300,
// // shape: {
// // width: 20,
// // height: 20
// // },
// // style: {
// // fill: '#f4be6a' // 设置第二个色块的颜色
// // },
// // z: 100
// // },
// // {
// // type: 'text',
// // left: 325,
// // style: {
// // text: '五线', // 设置第二个色块的含义
// // fill: '#f4be6a',
// // fontSize: 12
// // },
// // z: 100
// // }
// ]
// }
//
// ]
// };
var option = {
dataset: {
source:data.data
// source: [
// ['延时', '节点'],
// [89.3, 'Matcha Latte'],
// [57.1, 'Milk Tea'],
// [74.4, 'Cheese Cocoa'],
// [50.1, 'Cheese Brownie'],
// [89.7, 'Matcha Cocoa'],
// [68.1, 'Tea'],
// [19.6, 'Orange Juice'],
// [10.6, 'Lemon Juice'],
// [32.7, 'Walnut Brownie']
// ]
},
grid: {containLabel: true},
xAxis: {name: 'amount'},
yAxis: {type: 'category'},
visualMap: {
orient: 'horizontal',
left: 'center',
min: 0,
max: 50,
text: ['High Score', 'Low Score'],
// Map the score column to color
dimension: 0,
inRange: {
color: ['#11da11', '#E15457']
}
},
series: [
{
type: 'bar',
encode: {
// Map the "amount" column to X axis.
x: 'score',
// Map the "product" column to Y axis
y: 'product'
}
}
]
};
myChart1.setOption(option,true)
window.addEventListener("resize", () => {
myChart1.resize();
});
}
},
beforeDestroy() {
myChart1.clear()
},
destroyed() {
myChart1.clear()
},
mounted(){
// this.bingfn(this.bing)
},
}
</script>
<style scoped="scoped">
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="home" ref="appRef">
<div class="show">
<div><span style="float: left;font-size: 20px;margin-left: 2%;color: #00C0FF;margin-top: 1%">{{"目标IP:"+parentLevelRow.target}}</span></div>
<div><span style="float: left;font-size: 20px;margin-left: 2%;color: #00C0FF;margin-top: 1%">{{"目标IP: "+parentLevelRow.target}}</span></div>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='目标时延') ? '#f8fdff': '#565e6e'}" @click="updateTag('目标时延')">目标时延:</el-tag>
<el-tag class="tags1" :style="{'color': (tag1==='ICMP/v6延时') ? '#f8fdff': '#565e6e'}" @click="updateTag1('ICMP/v6延时')">ICMP/v6延时</el-tag>
@@ -12,10 +12,9 @@
<div v-if="tag==='应答内容'" class="answer">
<div style="display: flex;margin-right: 5px;">
<span style="margin-right: 2%;font-size: 25px;width: 15%">目标域名:</span>
<el-input v-model="input" placeholder="请输入目标IP"></el-input>
<el-input v-model="input" placeholder="请输入目标域名"></el-input>
</div>
<el-button type="primary" style="margin-top: 2%;margin-bottom: 10%">查询</el-button>
</div>
<div class="top" v-if="tag==='目标时延'">
<div class="top-left">
@@ -52,7 +51,7 @@
</div>
<!-- <TargetView class="top-right"/>-->
</div>
<NodeView class="bottom" v-if="tag==='目标时延'"></NodeView>
<NodeView class="bottom" v-if="tag==='目标时延'" :taskid=parentLevelRow.id></NodeView>
</div>
</div>
</template>
@@ -62,21 +61,20 @@ import TargetView from './module/target.vue'
import ImageView from './module/image.vue'
import SourceView from './module/source.vue'
import NodeView from './module/node.vue'
export default {
name: 'home',
name: 'menuMBZTGZ',
components: { TargetView, ImageView, SourceView, NodeView },
created() {
this.parentLevelRow = this.$route.query.row;
this.input=this.parentLevelRow.target_domain;
this.left1data.taskid = this.parentLevelRow.id;
this.left1data.target = this.parentLevelRow.target;
},
// mounted() {
// this.$router.push('/range/home')
// },
methods:{
updateTag(val){
if(val==='目标时延'){
this.tag=val
@@ -85,14 +83,13 @@ export default {
this.tag=val
this.tag1=""
}
},
updateTag1(val){
this.tag1=val;
this.tag='目标时延';
this.left1data.type=this.changePeram(val);
this.left1data.target=this.parentLevelRow.target;
this.left1data.target=this.parentLevelRow.target; // 任务目标IP
this.left1data.taskid=this.parentLevelRow.id; // 任务id
},
changePeram(val){
switch (val) {
@@ -114,7 +111,8 @@ export default {
data() {
return {
left1data:{
target:'1.1.1.1',
taskid:'',
target:'',
type:'icmp',
},
leftYdata:[],

View File

@@ -4,7 +4,7 @@
<div class="list" >
<el-table class="custom-table"
ref="multipleTable"
:data="tableData"
:data="nodes"
height="100%"
style="width: 100%;"
tooltip-effect="dark"
@@ -16,42 +16,39 @@
</template>
</el-table-column>
<el-table-column align="center"
prop="time"
label="时间"
prop="Id"
label="ID"
min-width="100">
</el-table-column>
<el-table-column align="center"
prop="level"
label="日志级别"
prop="Ip"
label="IP地址"
min-width="100">
</el-table-column>
<el-table-column align="center" label="日志信息" min-width="300">
<template slot-scope="scope" >{{ scope.row.info.substr(0, 80)}}</template>
</el-table-column>
<el-table-column align="center"
prop="user"
label="用户账号"
prop="Name"
label="节点名称"
min-width="100">
</el-table-column>
<el-table-column align="center"
prop="ip"
label="用户IP"
prop="Loc"
label="节点位置"
min-width="100">
</el-table-column>
</el-table>
</div>
<!-- <el-pagination-->
<!-- background-->
<!-- :current-page="page"-->
<!-- :page-sizes="[10, 20, 30, 40]"-->
<!-- :page-size="10"-->
<!-- :total="total"-->
<!-- layout="total, sizes, prev, pager, next, jumper"-->
<!-- @size-change="handleSizeChange"-->
<!-- @current-change="handleCurrentChange"-->
<!-- >-->
<!-- </el-pagination>-->
<!-- <el-pagination
background
:current-page="page"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination> -->
</div>
</template>
@@ -64,285 +61,27 @@ export default {
name: 'NodeView',
data() {
return {
tableData:[],
total:0,
page:1,
size:10,
time1:'',
time2:'',
log_level:"",
userAccount:"",
pickerOptions: {
shortcuts: [{
text: '今天',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: '昨天',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.$emit('pick', date);
}
}, {
text: '一周前',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', date);
}
}]
},
log_levels:[
{
value:'ERROR',
label:'ERROR'
},
{
value:'INFO',
label:'INFO'
},
{
value:'WARNING',
label:'WARNING'
},
],
nodes:[]
}
},
props:['taskid'],
created() {
this.query()
},
mounted(){
// this.init()
},
methods: {
handleSizeChange(val) {
console.log(`每页 ${val}`);
this.size=val
this.query()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.page=val
this.query()
},
// 请求节点信息
query(){
let data={
"per_page":this.size,
"page":this.page,
"begin":this.time1.toString(),
"end":this.time2.toString(),
"level":this.log_level,
"user":this.userAccount,
const reqParams = {
"taskid": this.taskid,
}
// 使用 Object.entries() 来遍历对象的键值对,并过滤掉值为空的属性
const filteredData = {};
for (const [key, value] of Object.entries(data)) {
if (value !== "") {
filteredData[key] = value;
}
}
this.$axios.get(this.$http.api.sysLog,filteredData).then(res=>{
console.log('getImages======',res)
if(res.code===200){
this.tableData=res?.log_data
// this.total=res?.total
this.total=100
}
}).catch(err=>{
this.loading = true
this.$axios.get(this.$http.api.targetNodes, reqParams).then(res => {
this.nodes = res.nodes
console.log("Node Page Nodes: ", this.nodes)
}).catch(err => {
console.log(err)
})
},
// async init() {
// await this.getRangeDict()
// await this.initData()
// this.initEcharts()
// },
// initData() {
// // this.nodeData = getTargetsResponse?.result
// // for (const key in this.nodeData) {
// // this.nodeDataX.push(key)
// // this.nodeDataY.push(this.nodeData[key])
// // }
//
// const reqParams = {}
// if (this.target_id) {
// reqParams.target_id = this.target_id
// }
// if (this.activeName !== 'total') {
// reqParams.status = this.activeName
// }
// this.loading = true
// return this.$axios.get(this.$http.api.getNodeStatistics, reqParams).then(res => {
// if (res.code == 200 || res.code == "OK") {
// this.nodeDataX = []
// this.nodeDataY = []
// this.nodeData = res?.result || {}
// for (const key in this.nodeData) {
// this.nodeDataX.push(key)
// this.nodeDataY.push(this.nodeData[key])
// }
// }
// }).catch(err => {
// console.log(err)
// }).finally(() => {
// this.loading = false
// })
// },
// 获取靶场列表字典
// getRangeDict() {
// const reqParams = {
// page: 1,
// size: 99,
// }
// return this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
// if (res.code == 200 || res.code == "OK") {
// this.rangeDict = res?.result?.items.map(item => {
// return {
// label: item.target_name,
// value: item.id
// }
// })
// }
// }).catch(err => {
// console.log(err)
// }).finally(() => {
// this.rangeDict.unshift({label: '全部靶场', value: ''})
// this.target_id = this.rangeDict[0].value
// this.target_name = this.rangeDict[0].label
// })
// },
// 改变靶场
// async changeRange(val) {
// this.target_name = this.rangeDict.find(item => item.value === val)?.label
// await this.initData()
// this.refreshData()
// },
// 改变靶场状态
// async changeRangeStatus() {
// await this.initData()
// this.refreshData()
// },
// 刷新图标数据
// refreshData(){
// if(!this.nodeChart){
// return
// }
// //更新数据
// var option = this.nodeChart.getOption()
// option.title[0].text = `${this.target_name}节点统计`,
// option.xAxis[0].data = this.nodeDataX
// option.series[0].data = this.nodeDataY
// this.nodeChart.setOption(option)
// },
// 切换靶场状态
// tabClick(tab, event) {
// this.changeRangeStatus()
// },
//初始化节点柱状图
// initEcharts() {
// var chartDom = document.getElementById('node');
// this.nodeChart = echarts.init(chartDom);
// var option;
//
// option = {
// title: {
// text: `${this.target_name}节点统计`,
// left: 'center',
// top: '15',
// textStyle: {
// color: '#FFFFFF',
// fontSize: 16,
// fontWeight: 500
// }
// },
// tooltip: {
// // trigger: 'axis',
// axisPointer: {
// // type: 'shadow'
// }
// },
// grid: {
// top: '25%',
// left: '3%',
// right: '4%',
// bottom: '8%',
// containLabel: true
// },
// xAxis: [
// {
// type: 'category',
// data: this.nodeDataX,
// axisTick: {
// // alignWithLabel: true
// show: false
// },
// axisLabel: {
// fontSize: '14',
// color: 'rgba(255, 255, 255, 0.7)'
// }
// }
// ],
// yAxis: [
// {
// type: 'value',
// name: '靶场中各类节点数量',
// nameTextStyle: {
// color: 'rgba(255, 255, 255, 0.7)',
// padding: [0, 0, 0, 70]
// },
// axisLabel: {
// fontSize: '12',
// color: 'rgba(255, 255, 255, 0.7)'
// },
// splitLine: {
// show: true,
// lineStyle: {
// color: ['rgba(199, 199, 200, 0.1)'],
// width: 1,
// type: 'dashed'
// }
// }
// }
// ],
// series: [
// {
// // name: 'Direct',
// type: 'bar',
// barWidth: '20%',
// data: this.nodeDataY,
// itemStyle: {
// normal: {
// label: {
// show: true, // 是否显示
// position: 'top', // 显示位置
// color: '#FFFFFF',
// formatter: function (params) {
// // 核心部分 formatter 可以为字符串也可以是回调
// if (parseInt(params.value) === 0) {
// return '';
// } else {
// return params.data.label;
// }
// }
// },
// // 每个柱子的颜色即为colorList数组里的每一项如果柱子数目多于colorList的长度则柱子颜色循环使用该数组
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// { offset: 0, color: '#3280D7' },
// { offset: 1, color: '#4122E2' }
// ])
// }
// }
// }
// ]
// };
// option && this.nodeChart.setOption(option);
// }
}
}
</script>

View File

@@ -25,6 +25,7 @@ export default {
// required: true,
default: function() {
return {
taskid:'',
target:'',
type: '',
}
@@ -69,21 +70,24 @@ export default {
// this.getRangeDict()
},
methods: {
// 获取数据
querydelay(){
const reqParams = {
"target": this.left1data.target,
"taskid": this.left1data.taskid,
"type": this.left1data.type,
}
this.loading = true
this.$axios.get(this.$http.api.targetDelay, reqParams).then(res => {
if (res.code == 200) {
this.zhudouble.xdata = []
this.zhudouble.ydata1 = []
for (let i = 0; i <res.delay_data.length>10?10:res.delay_data.length ; i++) {
this.zhudouble.xdata.push(res.delay_data[i].Id);
this.zhudouble.ydata1.push(res.delay_data[i].CurrDelay);
}
}
this.$refs.zhudouble.bingfn(this.zhudouble)
}).catch(err => {
console.log(err)
}).finally(() => {

View File

@@ -1,30 +1,20 @@
<template>
<div class="source-card">
<!-- <div class="source-chart" id="resource" v-loading="loading"></div>-->
<ZhuZhuangTu_jbs :active="0" ref="zhudouble" :bing="zhudouble"></ZhuZhuangTu_jbs>
<!-- <div class="tabs-container">-->
<!-- <el-tabs v-model="activeName" @tab-click="tabClick">-->
<!-- <el-tab-pane label="CPU" name="CPU"></el-tab-pane>-->
<!-- <el-tab-pane label="MEMEORY" name="MEMEORY"></el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </div>-->
<NodeWordMap :nodes="nodes"></NodeWordMap>
</div>
</template>
<script>
import * as echarts from 'echarts';
import ZhuZhuangTu_jbs from '../echarts/zhuzhuangtu_jbs'
import NodeWordMap from '../echarts/nodewordmap'
import { getTargetsResponse } from './sourceMock.js'
export default {
name: 'TargetView',
props: {
left1data: {
type: Object,
// required: true,
default: function() {
return {
taskid:'',
target:'',
type: '',
}
@@ -40,196 +30,32 @@
deep: true
}
},
name: 'SourceView',
components: {
ZhuZhuangTu_jbs,
},
components: { NodeWordMap },
data() {
return {
loading: false,
sourceData: {},
sourceDataX: [],
sourceDataY: [],
cpuList: [],
memoryList: [],
activeName: 'CPU',
sourceChart: null,
rangeDict: [],
zhudouble:{
data:[
['延时', '节点'],
],
},
nodes:[]
}
},
methods: {
querydelay() {
const reqParams = {
"taskid": this.left1data.taskid,
}
this.loading = true
this.$axios.get(this.$http.api.targetNodes, reqParams).then(res => {
this.nodes = res.nodes
// console.log("Nodes: ", this.nodes)
}).catch(err => {
console.log(err)
})
},
},
created() {
},
mounted(){
this.init()
// this.getRangeDict()
this.querydelay()
console.log("**********target mounted")
},
methods: {
init() {
const reqParams = {
"target": this.left1data.target,
"type": this.left1data.type,
}
console.log("GGGGGGGGGGGGGGGGGGGG")
console.log(reqParams)
console.log("GGGGGGGGGGGGGGGGGGGG")
this.loading = true
this.$axios.get(this.$http.api.targetDelay,reqParams).then(res => {
if (res.code == 200) {
for (let i = 0; i <res.delay_data.length>10?10:res.delay_data.length ; i++) {
let A=[];
A.push(res.delay_data[i].CurrDelay);
A.push(res.delay_data[i].Id);
this.zhudouble.data.push(A);
}
this.$refs.zhudouble.bingfn(this.zhudouble)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
this.initEcharts()
})
},
initEcharts() {
let that = this
var chartDom = document.getElementById('resource');
this.sourceChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '靶场资源使用排序',
left: '10',
top: '15',
textStyle: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: 500
}
},
tooltip: {
// trigger: 'axis',
axisPointer: {
// type: 'shadow'
}
},
grid: {
top: '25%',
left: '3%',
right: '4%',
bottom: '8%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: this.sourceDataX,
axisTick: {
// alignWithLabel: true
show: false
},
axisLabel: {
fontSize: '14',
color: 'rgba(255, 255, 255, 0.7)'
}
}
],
yAxis: [
{
type: 'value',
axisLabel: {
fontSize: '12',
color: 'rgba(255, 255, 255, 0.7)',
formatter: function(value) {
return value + ' (mcore)'
}
},
splitLine: {
show: true,
lineStyle: {
color: ['rgba(199, 199, 200, 0.1)'],
width: 1,
type: 'dashed'
},
}
}
],
series: [
{
// name: 'Direct',
type: 'bar',
barWidth: '20%',
data: this.sourceDataY,
itemStyle: {
normal: {
label: {
show: true, // 是否显示
position: 'top', // 显示位置
color: '#FFFFFF',
formatter: function (params) {
// 核心部分 formatter 可以为字符串也可以是回调
if (parseInt(params.value) === 0) {
return '';
} else {
return params.data.label;
}
}
},
// 每个柱子的颜色即为colorList数组里的每一项如果柱子数目多于colorList的长度则柱子颜色循环使用该数组
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#01FFFF' },
{ offset: 1, color: '#2160B8' }
])
}
}
}
]
};
option && this.sourceChart.setOption(option);
//点击事件
this.sourceChart.on('click', function(params) {
const target_name = params.name
const target_id = that.rangeDict.find(item => item.label === target_name)?.value
that.$store.commit('globalAttrs/setCheckMenu', 'rangeConfigManage')
that.$store.commit('range/setTargetId', target_id)
});
},
// 刷新图标数据
refreshData(data, tabName){
if(!this.sourceChart){
return
}
//更新数据
var option = this.sourceChart.getOption()
option.series[0].data = data
option.yAxis[0].axisLabel.formatter = function(value) {
if (tabName === 'MEMEORY') {
return value + ' (Mi)'
} else {
return value + ' (mcore)'
}
}
this.sourceChart.setOption(option)
},
tabClick(tab, event) {
switch (tab.name) {
case 'CPU':
this.refreshData(this.cpuList, 'CPU')
break;
case 'MEMEORY':
this.refreshData(this.memoryList, 'MEMEORY')
break;
default:
this.refreshData(this.cpuList, 'CPU')
break;
}
},
}
}
</script>

View File

@@ -6,17 +6,18 @@
></Header>
<div class="header2">
<div style="width: 45%; display: inline-block;">
<el-descriptions class="custom-descriptions" >
<el-descriptions-item label="任务名称" >{{parentLevelRow.name}}</el-descriptions-item>
<el-descriptions-item label="代理编号">{{parentLevelRow.id}}</el-descriptions-item>
<el-descriptions-item label="执行状态">
<el-descriptions class="custom-descriptions">
<el-descriptions-item label="任务名称" class="cell-item">{{parentLevelRow.name}}</el-descriptions-item>
<el-descriptions-item label="代理编号" class="cell-item">{{parentLevelRow.id}}</el-descriptions-item>
<el-descriptions-item label="执行状态" class="cell-item">
<el-tag type="info" v-if="parentLevelRow.status==='stop'">暂停</el-tag>
<el-tag type="warning" v-if="parentLevelRow.status==='working'">执行中</el-tag>
<el-tag type="danger" v-if="parentLevelRow.status==='finish'">已完成</el-tag>
</el-descriptions-item>
<el-descriptions-item label="任务目标">
<el-descriptions-item label="任务目标" class="cell-item">
{{parentLevelRow.target}}
<span style="color: #00C0FF;margin-left: 5%" @click="getInfoByIp(parentLevelRow)">查看实时状态</span>
<!-- <span style="color: #00C0FF;margin-left: 5%" @click="getInfoByIp(parentLevelRow)">查看实时状态</span> -->
<span style="color: #00C0FF;margin-left: 5%" @click="getInfoById(parentLevelRow)">查看实时状态</span>
</el-descriptions-item>
</el-descriptions>
</div>
@@ -98,7 +99,7 @@
label="执行输出"
min-width="150">
<template slot-scope="scope">
<el-button type="primary" @click="newlyPztj=!newlyPztj,getLog(scope.row.policy_id)">查看</el-button>
<el-button type="primary" @click="newlyPztj=!newlyPztj, getLog(scope.row.policy_id)">查看</el-button>
</template>
</el-table-column>
<el-table-column
@@ -200,7 +201,7 @@ import Header from './module/Header.vue'
import UserForm from './module/UserForm.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "RangeConfigManage",
name: "menuTaskInfo",
components:{ Header, UserForm },
data(){
return{
@@ -227,12 +228,15 @@ export default {
},
watch: {},
created() {
this.parentLevelRow = this.$route.query.row;
this.parentLevelRow = JSON.parse(this.$route.query.row);
this.init()
},
methods:{
getInfoByIp(val){
this.$router.push({ path: 'menuMBZTGZ', query: { row: val } });
// getInfoByIp(val){
// this.$router.push({ path: 'menuMBZTGZ', query: { row: val } });
// },
getInfoById(val){
this.$router.push({ path: 'menuMBZTGZ', query: { row: val } });
},
CustomizationImageFales(){
this.newlyPztj=false;
@@ -296,9 +300,9 @@ export default {
this.$axios.get(this.$http.api.taskInfoLog, reqParams).then(res => {
// if (res.code == 200 || res.code == "OK") {
// this.total = res?.data?.total
this.total = 100
this.total = 10
this.tableDataLog = res?.data
this.tableData.map(item => {
this.tableDataLog.map(item => {
item.permissions.map(permi => {
this.$set(permi, 'delLoading', false)
return permi
@@ -326,6 +330,7 @@ export default {
// this.total = res?.data?.total
this.total = 100
this.tableDataLog = res?.data
console.log("***********", this.tableDataLog)
this.tableData.map(item => {
item.permissions.map(permi => {
this.$set(permi, 'delLoading', false)
@@ -473,6 +478,10 @@ export default {
/* float: right;*/
/*}*/
}
.cell-item {
display: flex;
align-items: center;
}
/*.custom-paginationLog /deep/ .el-pagination /deep/ .is-background {*/
/* float: right;*/
/*}*/
@@ -585,7 +594,7 @@ export default {
width: 100%;
height: auto;
margin-bottom: 1%;
margin-left: 2.5%;
margin-left: 2%;
.enlarged-button {
transform: scale(1.5); /* 将按钮和图标整体放大 1.5 倍 */
margin-left: 3%;
@@ -596,8 +605,6 @@ export default {
/deep/ .el-descriptions-item{
background-color: rgba(19, 34, 99, 1);
}
}
/*.custom-descriptions /deep/ .el-descriptions{*/
/* background-color: #f04247; !* 设置背景颜色为灰色 *!*/

View File

@@ -15,7 +15,7 @@
element-loading-text="加载中..."
height="100%"
style="width: 100%;"
:data="tableData"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
>
@@ -88,7 +88,7 @@ import Header from './module/Header.vue'
import UserForm from './module/UserForm.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "RangeConfigManage",
name: "menuTaskManagement",
components:{ Header, UserForm },
data(){
return{
@@ -118,18 +118,18 @@ export default {
...params
}
this.loading = true
this.$axios.get(this.$http.api.taskList, reqParams).then(res => {
this.$axios.get(this.$http.api.taskList, reqParams).then(res => { // 获取任务信息
// if (res.code == 200 || res.code == "OK") {
// this.total = res?.data?.total
this.total = 100
this.tableData = res?.data
this.tableData.map(item => {
item.permissions.map(permi => {
this.$set(permi, 'delLoading', false)
return permi
})
return item
})
this.tableData = res?.data // 可选链Optional Chaining操作符
// this.tableData.map(item => {
// item.permissions.map(permi => {
// this.$set(permi, 'delLoading', false)
// return permi
// })
// return item
// })
// }
}).catch(err => {
console.log(err)
@@ -201,27 +201,28 @@ export default {
},
// 详情
taskInfo(val) {
this.$router.push({ path: 'menuTaskInfo', query: { row: val } });
},
// 获取权限字典
getPermissionDict() {
const params = {
page: 1,
size: 99
}
this.$axios.get(this.$http.api.getPermissionList, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.permissionDict = res?.result?.items.map(item => {
return {
label: item.permission_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
// this.$router.push({ path: 'menuTaskInfo', query: { row: val } });
this.$router.push({ path: 'menuTaskInfo', query: { row: JSON.stringify(val) } });
},
// // 获取权限字典
// getPermissionDict() {
// const params = {
// page: 1,
// size: 99
// }
// this.$axios.get(this.$http.api.getPermissionList, params).then(res => {
// if (res.code == 200 || res.code == "OK") {
// this.permissionDict = res?.result?.items.map(item => {
// return {
// label: item.permission_name,
// value: item.id
// }
// })
// }
// }).catch(err => {
// console.log(err)
// })
// },
// 修改每页数据条数
handleSizeChange(val) {
console.log(`每页 ${val}`)

View File

@@ -49,7 +49,6 @@
<el-option
v-for="item in strategy"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
@@ -134,10 +133,12 @@ export default {
{ value: 'ddos', label: '拒绝服务', type: 'warning' },
{ value: 'sjqp', label: '数据欺骗', type: 'warning' },
],
agencyChange:[
{ value: '中国北京', label: '中国北京', type: 'success' },
{ value: '美国纽约', label: '美国纽约', type: 'warning' },
],
// 代理选择列表
// agencyChange:[
// { value: '中国北京', label: '中国北京', type: 'success' },
// { value: '美国纽约', label: '美国纽约', type: 'warning' },
// ],
agencyChange:[],
stateAwareMode:[
{ value: 'auto', label: '自动选择', type: 'success' },
{ value: 'tcp', label: 'TCP时延', type: 'warning' },
@@ -149,10 +150,11 @@ export default {
{ value: 'now', label: '立刻执行', type: 'success' },
{ value: 'man', label: '手动执行', type: 'warning' },
]
}
},
created(){
this.getAgenayList();
},
methods: {
getTagsByIP(val){
let data={
@@ -165,8 +167,25 @@ export default {
}).catch(err=>{
console.log(err)
})
},
// 获取代理信息
getAgenayList(){
let data={
"atype":"gjst",
"status":1,
"idle":1
}
this.$axios.get(this.$http.api.agentQueryList,data).then(res=>{
if(res.code===200){
console.log("*****Length", res.total)
for (let i=0; i<res.total; i++) {
let id = res.agent_data[i].id
this.agencyChange.push({value:id})
}
}
}).catch(err=>{
console.log(err)
})
},
close() {
this.resetForm()
@@ -175,16 +194,17 @@ export default {
},
submit() {
let data={
"target":this.form.domain,
// "target":this.form.domain,
"agent":this.form.agencyChange,
"name":this.form.name,
"target_domain":"",
"target_rr":this.form.inject,
"policy_time":this.form.switchoverTime,
"run_time":this.form.executeTime,
"run_flag":this.form.operationalConfiguration,
"policy":this.form.strategy,
"policy_time":this.form.switchoverTime,
"run_flag":this.form.operationalConfiguration,
"run_time":this.form.executeTime,
"scan":this.form.stateAwareMode,
"target": this.form.ip,
"target_domain":this.form.domain,
"target_rr":this.form.inject,
}
console.log("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
console.log(data)

View File

@@ -1,729 +0,0 @@
<template>
<div class="range-config-manage" ref="appRef">
<div class="show">
<Header
:range-dict="rangeDict"
:batch-deploy-loading="batchDeployLoading"
:batch-del-loading="batchDelLoading"
:batch-pipeline-loading="batchPipelineLoading"
:disabled-batch-pipline="disabledBatchPipline"
@addConfig="addConfig"
@query="query"
@batchDeploy="batchDeploy"
@batchDel="batchDel"
@batchPipeline="batchPipeline"
></Header>
<div class="list" >
<el-table
class="custom-table"
ref="multipleTable"
v-loading="loading"
element-loading-text="加载中..."
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@cell-mouse-enter="cellEnter"
@selection-change="handleSelectionChange"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
type="index"
label="序号"
width="80"/>
<el-table-column
align="center"
prop="tor_version"
label="Tor版本"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
<span style="padding-left: 5px;">{{ roleDict[scope.row.role] || scope.row.role }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
prop="replicas"
label="复本数"
min-width="100"/>
<el-table-column
align="center"
prop="service"
label="服务"
min-width="150"/>
<el-table-column
align="center"
prop="bandwidth"
label="宽带"
min-width="100"/>
<el-table-column
align="center"
prop="memory"
label="内存"
min-width="100"/>
<el-table-column
align="center"
prop="out_port"
label="映射端口"
min-width="100"/>
<el-table-column
align="center"
prop="has_deployed"
label="部署状态"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.has_deployed ? '已部署' : '未部署' }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="network_id"
label="网络"
min-width="100">
<template slot-scope="scope">
<el-popover
placement="top"
width="180"
trigger="hover"
:visible-arrow="false"
popper-class= "pop-con"
:ref="`popover1` + scope.$index">
<div v-if="popVisiable === true" style="text-align: center; margin: 0;">
<p>国家{{popoverData.country}}</p>
<p>网络{{popoverData.network}}</p>
</div>
<a
class="network"
slot="reference"
@mouseenter="openAction(scope.$index)"
@mouseleave="cancelAction(scope.$index)"
>
{{ `网络${scope.row.network_id}`}}
</a>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
min-width="300"
>
<template slot-scope="scope">
<el-button type="text" size="medium" :disabled="scope.row.delLoading" @click="edit(scope.row)">修改</el-button>
<el-button type="text" size="medium" :disabled="scope.row.has_deployed==true || scope.row.delLoading" :loading="scope.row.deployLoading" @click="deploy(scope.row)">部署</el-button>
<el-button type="text" size="medium" :loading="scope.row.delLoading" @click="del(scope.row)">删除</el-button>
<el-button type="text" size="medium" :disabled="scope.row.delLoading" :loading="scope.row.restartLoading" @click="restart(scope.row)">重启</el-button>
<el-button type="text" size="medium" :disabled="scope.row.pipeline_id===0 || scope.row.delLoading" :loading="scope.row.pipelineLoading" @click="pipeline(scope.row)">运行流水线</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
background
:current-page="page"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
<div class="mask"></div>
<ConfigForm
ref="configForm"
:is-add="isAdd"
:target_id="target_id"
:other-role="otherRole"
:country-dict="countryDict"
@refresh="init"
@openRoleForm="openRoleForm"
@clearRoleForm="clearRoleForm">
</ConfigForm>
<RoleForm
ref="roleForm"
v-model="otherRole"
@submitOtherRole="submitOtherRole">
</RoleForm>
</div>
</div>
</template>
<script>
import Header from './module/Header.vue'
import ConfigForm from './module/ConfigForm.vue'
import RoleForm from './module/RoleForm.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "RangeConfigManage",
components:{ Header, ConfigForm, RoleForm },
// props:['fTag','fInput','fFrom','FIsConfigQuery'],
data(){
return{
page: 1,
size: 10,
total: 0,
isAdd: false,
loading: false,
target_id: '',
otherRole: '',
tableData: [],
selectConfigIds: [],
countryDict: [],
rangeDict: [],
batchDeployLoading: false,
batchDelLoading: false,
batchPipelineLoading: false,
networkVisible: false,
popoverData: {},
popVisiable: false,
roleDict:{
da: '权威目录节点',
client: '客户端节点',
guard: '入口节点',
relay: '路由节点',
exit: '出口节点',
onion: '洋葱服务节点',
},
disabledBatchPipline: false,
delTimer: null,
counter: 0,
batchDelTimer: null,
batchCounter: 0,
}
},
mounted() {
},
watch: {
'$store.state.range.targetId': {
handler(newVal, oldVal) {
this.target_id = newVal
this.init()
},
immediate: true
}
},
created() {
this.getCountryDict()
this.getRangeDict()
},
destroyed() {
if(this.delTimer) {
clearInterval(this.delTimer)
this.delTimer = null
}
if(this.batchDelTimer) {
clearInterval(this.batchDelTimer)
this.batchDelTimer = null
}
this.counter = 0
this.batchCounter = 0
},
methods:{
init(params={}) {
// this.tableData = getTargetsResponse?.result?.items
// this.total = getTargetsResponse?.result?.total
// TODO: 暂时注释接口
const reqParams = {
page: this.page,
size: this.size,
...params
}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.loading = true
this.$axios.get(this.$http.api.getSettingList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.total = res?.result?.total
this.tableData = res?.result?.items
this.tableData.map(item => {
this.$set(item, 'deployLoading', false)
this.$set(item, 'delLoading', false)
this.$set(item, 'restartLoading', false)
this.$set(item, 'pipelineLoading', false)
return item
})
// 是否全部都没有流水线
this.disabledBatchPipline = this.tableData.findIndex(item => item.pipeline_id !== 0) === -1 ? true : false
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
query(params) {
this.init(params)
},
// 打开添加配置dialog
addConfig() {
this.isAdd = true
this.$refs.configForm.getTorDict()
this.$refs.configForm.getImageDict()
document.querySelector('.mask').style.display = 'block'
this.$refs.configForm.visible = true
},
// 当选择其它角色时,弹窗填写角色窗
openRoleForm() {
this.$refs.roleForm.visible = true
},
clearRoleForm() {
this.otherRole = ''
},
// 编辑Form重新请求镜像字典
submitOtherRole() {
this.$refs.configForm.getImageDict()
},
// 批量部署
batchDeploy() {
if (this.selectConfigIds.length === 0) {
this.$notify({
title: '请勾选要部署的数据!',
type: 'success',
duration: 2500
})
return
}
this.batchDeployLoading = true
this.$axios.post(this.$http.api.batchDeply, {setting_id_list: this.selectConfigIds}, {target_id: this.target_id}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '批量部署成功',
type: 'success',
duration: 2500
})
this.init()
} else {
this.$notify({
title: res,
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.batchDeployLoading = false
})
},
// 批量删除
batchDel() {
if (this.selectConfigIds.length === 0) {
this.$notify({
title: '请勾选要删除的数据!',
type: 'success',
duration: 2500
})
return
}
this.batchDelLoading = true
this.$axios.delete(this.$http.api.asynBatchDel, {}, {setting_id_list: this.selectConfigIds}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.init()
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.batchDelTimer = setInterval(() => {
this.getBatchTask(res?.result?.task_id || '')
},10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.batchDelLoading = false
})
},
// 批量运行流水线
batchPipeline() {
if (this.selectConfigIds.length === 0) {
this.$notify({
title: '请勾选要运行流水线的数据!',
type: 'success',
duration: 2500
})
return
}
this.batchPipelineLoading = true
this.$axios.post(this.$http.api.batchPipeline, {setting_id_list: this.selectConfigIds}, {}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '批量运行流水线成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.batchPipelineLoading = false
})
},
// 部署
deploy(row) {
row.deployLoading = true
this.$axios.post(this.$http.api.singleDeploy, {}, {setting_id: row.id}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '部署成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
row.deployLoading = false
})
},
// 删除
del(row) {
const url = this.$http.api.asynDelSetting + '/' + row.id
const params = {
user_id: row.user_id
}
row.delLoading = true
this.$axios.delete(url, params).then(res => {
if (res.code == 200 || res.code == "OK") {
// this.init()
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
if (this.delTimer) {
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
}
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
},10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
// row.delLoading = false
})
},
// 重启
restart(row) {
row.restartLoading = true
this.$axios.post(this.$http.api.singleDeploy, {}, { setting_id: row.id }).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '重启成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
row.restartLoading = false
})
},
// 运行流水线
pipeline(row) {
row.pipelineLoading = true
this.$axios.post(this.$http.api.pipeline, {}, { setting_id: row.id }).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '运行流水线成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
row.pipelineLoading = false
})
},
// 修改
edit(row) {
this.isAdd = false
this.$refs.configForm.setting_id = row.id
this.$refs.configForm.form.role = row.role
this.$refs.configForm.form.tor_version = row.tor_version
this.$refs.configForm.form.image_id = row.image_id
this.$refs.configForm.form.replicas = row.replicas
this.$refs.configForm.form.bandwidth = row.bandwidth
this.$refs.configForm.form.memory = row.memory
this.$refs.configForm.form.service = row.service
this.$refs.configForm.form.out_port = row.out_port
this.$refs.configForm.form.or_port = row.or_port
this.$refs.configForm.form.dir_port = row.dir_port
this.$refs.configForm.form.socks_port = row.socks_port
this.$refs.configForm.form.control_port = row.control_port
this.$refs.configForm.form.country_id = row.country_id
this.$refs.configForm.form.network_id = row.network_id
this.$refs.configForm.form.direct = row.direct
this.$refs.configForm.form.start_tcpdump = row.start_tcpdump
if(row.start_tcpdump) {
this.$refs.configForm.form.deployType = 'start'
} else {
if(row.direct) {
this.$refs.configForm.form.deployType = 'direct'
} else {
this.$refs.configForm.form.deployType = 'only'
}
}
this.$refs.configForm.getTorDict()
this.$refs.configForm.getImageDict()
this.$refs.configForm.getNetworkDict()
document.querySelector('.mask').style.display = 'block'
this.$refs.configForm.visible = true
},
// 获取国家字典
getCountryDict() {
const params = {
page: 1,
size: 99
}
this.$axios.get(this.$http.api.getCountryDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.countryDict = res?.result?.items.map(item => {
return {
label: item.country_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
// 获取靶场列表字典
getRangeDict() {
const reqParams = {
page: 1,
size: 99,
}
this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.rangeDict = res?.result?.items.map(item => {
return {
label: item.target_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.rangeDict.unshift({label: '全部靶场', value: ''})
})
},
// 处理选中的数据
handleSelectionChange(selectData) {
this.selectConfigIds = selectData.map(item => {
return item.id
})
},
// 修改每页数据条数
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.page=1
this.size=val
this.query()
},
// 修改页数
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.page=val
this.query()
},
// 获取网络
cellEnter(row, column){
if(column.label === "网络"){
this.popoverData = {}
this.popVisiable = false
// this.loading = true
const reqParams = {
network_id: row.network_id,
}
this.$axios.get(this.$http.api.getNetwork, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.popoverData = res.result
this.popVisiable = true;
}
}).catch(err => {
console.log(err)
}).finally(() => {
// this.loading = false
})
}
},
cancelAction(index){
let refName = "popover1" + index;
this.$refs[refName].doClose();
},
openAction(index){
let refName = "popover1" + index;
this.$refs[refName].doShow();
},
// 获取任务进度
getTask(task_id) {
const url = this.$http.api.task + '/' + task_id
this.$axios.get(url, {}).then(res => {
if(res.task_status !== 'PENDING' && res.task_status !== 'STARTED') {
if (res?.task_result?.code === 500 || res?.task_result?.message === 'false') {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行失败`,
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行完毕`,
type: 'success',
duration: 2500
})
}
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.counter++
if (this.counter >= 60) {
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.init()
}
})
},
// 获取任务进度
getBatchTask(task_id) {
const url = this.$http.api.task + '/' + task_id
this.$axios.get(url, {}).then(res => {
if(res.task_status !== 'PENDING' && res.task_status !== 'STARTED') {
if (res?.task_result?.code === 500 || res?.task_result?.message === 'false') {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行失败`,
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行完毕`,
type: 'success',
duration: 2500
})
}
clearInterval(this.batchDelTimer)
this.batchDelTimer = null
this.batchCounter = 0
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.batchCounter++
if (this.batchCounter >= 30) {
clearInterval(this.batchDelTimer)
this.batchDelTimer = null
this.batchCounter = 0
this.init()
}
})
}
}
}
</script>
<style lang='less' scoped="scoped">
.custom-table {
width: 100%;
height: 100%;
}
.range-config-manage{
width: 100%;
height: 100%;
/*background-color: #010f4e;*/
// background-color: rgba(255, 25, 49, 0.4);
float: right;
position: relative; /* 确保相对定位生效 */
.show{
width: 95%;
height: 95%;
/*background-color: #67c23a;*/
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../img/backgroundFourCorner.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
/*background-size: cover; !* 宽度为100%,高度自适应保持宽高比 *!*/
/*display: flex; !* 将容器设置为 Flex 容器 *!*/
/*justify-content: center; !* 水平居中子元素 *!*/
/*align-items: center; !* 垂直居中子元素 *!*/
.list{
width: 95%;
height: 84%;
margin-left: 2.5%;
overflow-y: auto;
overflow-y: scroll;
overflow-x: hidden;
border: none;
::v-deep .network {
float: right;
cursor: pointer;
font-size: 14px;
color: #857BFF;
text-decoration: none; /* 取消默认的下划线 */
}
}
.list::-webkit-scrollbar {
width: 0px; /* 隐藏滚动条 */
height: 0px;
background-color: transparent; /* 让背景透明 */
}
/* 隐藏火狐浏览器滚动条 */
@-moz-document url-prefix() {
.trackSource {
scrollbar-width: none;
}
}
// 遮罩层
.mask{
position: fixed; /*将元素设置为固定定位*/
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /*通过rgba函数来控制遮罩层的透明度*/
display: none; /*将元素隐藏*/
}
}
}
</style>

View File

@@ -1,718 +0,0 @@
const getTargetsResponse = {
"code": 200,
"message": "success",
"result": {
"total": 32,
"page": 1,
"size": 20,
"pages": 0,
"items": [
{
"tor_version": "4.6.10",
"role": "da",
"replicas": 1,
"service": "string",
"bandwidth": 10,
"memory": 100,
"image_id": 0,
"out_port": 0,
"has_deployed": false,
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 0,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "relay",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 1,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "exit",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 2,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "onion",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 3,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "client",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 4,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "guard",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 5,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 6,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 7,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"tor_version": "4.6.10",
"role": "string",
"replicas": 1,
"service": "string",
"bandwidth": 10,
"memory": 100,
"image_id": 0,
"out_port": 0,
"has_deployed": false,
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 0,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 1,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 2,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 3,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 4,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 5,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 6,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 7,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"tor_version": "4.6.10",
"role": "string",
"replicas": 1,
"service": "string",
"bandwidth": 10,
"memory": 100,
"image_id": 0,
"out_port": 0,
"has_deployed": false,
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 0,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 1,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 2,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 3,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 4,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 5,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 6,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 7,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"tor_version": "4.6.10",
"role": "string",
"replicas": 1,
"service": "string",
"bandwidth": 10,
"memory": 100,
"image_id": 0,
"out_port": 0,
"has_deployed": false,
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 0,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 1,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 2,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 3,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 4,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 5,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 6,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
},
{
"role": "string",
"image_id": 0,
"replicas": 1,
"bandwidth": 10,
"memory": 100,
"service": "string",
"tor_version": "4.6.10",
"create_time": "2023-12-19T14:17:19.290Z",
"update_time": "2023-12-19T14:17:19.290Z",
"out_port": 0,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"id": 7,
"has_deployed": false,
"start_tcpdump": 0,
"user_id": 0,
"target_id": 0,
"network_id": 0
}
]
}
}
export { getTargetsResponse }

View File

@@ -1,658 +0,0 @@
<template>
<div class="config-dialog" v-if="visible">
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='基本配置') ? '#f8fdff': '#565e6e'}">基本配置</el-tag>
<el-tag class="tags" :style="{'color': (tag==='网络配置') ? '#f8fdff': '#565e6e'}">网络配置</el-tag>
<el-tag class="tags" :style="{'color': (tag==='部署') ? '#f8fdff': '#565e6e'}">部署</el-tag>
</div>
<div class="basic-box" v-if="tag==='基本配置'">
<el-form
ref="basicForm"
:model="form"
:rules="rules"
label-width="150px"
class="basic-form"
size="small"
>
<el-row>
<el-col :span="20">
<el-form-item label="角色选择" prop="role">
<el-select v-model="form.role" placeholder="请选择角色" @change="selectRole">
<el-option
v-for="item in roleDict"
:key="item.value"
:label="item.value === 'other' ? `${item.label}(${otherRole})` : `${item.label}(${item.value})`"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role!=='other'">
<el-col :span="20">
<el-form-item label="tor 版本" prop="tor_version" :rules="form.role!=='other' ? rules.tor_version : [{required: false}]">
<el-select v-model="form.tor_version" placeholder="tor 版本" @change="selectTor">
<el-option
v-for="item in torDict"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="选择镜像" prop="image_id">
<el-select v-model="form.image_id" placeholder="镜像名">
<el-option
v-for="item in imageDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="复本数" prop="replicas">
<el-input v-model="form.replicas" placeholder="请输入内容 (默认值是1)"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="带宽限制" prop="bandwidth">
<el-input v-model="form.bandwidth" placeholder="请输入内容 (默认值是10)">
<p slot="suffix" class="el-input__icon">M</p>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="内存限制" prop="memory">
<el-input v-model="form.memory" placeholder="请输入内容 (默认值是100)">
<p slot="suffix" class="el-input__icon">Mi</p>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role==='onion'">
<el-col :span="20">
<el-form-item label="服务器地址" prop="service" :rules="form.role==='onion' ? rules.service : [{required: false}]">
<el-input v-model="form.service" placeholder="请输入内容 (onion角色必填)"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role==='client'">
<el-col :span="20">
<el-form-item label="代理端口" prop="out_port" :rules="form.role==='client' ? rules.out_port : [{required: false}]">
<!-- <el-input v-model="form.out_port" placeholder="请输入内容 (client角色必填范围30000-32767)"></el-input> -->
<el-select v-model="form.out_port" placeholder="请选择代理端口" @visible-change="showOutPortSelect">
<el-option
v-for="item in outPortDict"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role==='da'||form.role==='relay'||form.role ==='guard'||form.role ==='exit'">
<el-col :span="20">
<el-form-item label="or端口" prop="or_port">
<el-input v-model="form.or_port" placeholder="请输入内容 (默认值是7000)"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role==='da'||form.role==='relay'||form.role ==='guard'||form.role ==='exit'">
<el-col :span="20">
<el-form-item label="dir端口" prop="dir_port">
<el-input v-model="form.dir_port" placeholder="请输入内容 (默认值是9030)"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role ==='onion'||form.role==='client'">
<el-col :span="20">
<el-form-item label="客户端监听端口" prop="socks_port">
<el-input v-model="form.socks_port" placeholder="请输入内容 (默认值是19001)"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="form.role ==='onion'||form.role==='client'">
<el-col :span="20">
<el-form-item label="客户端控制端口" prop="control_port">
<el-input v-model="form.control_port" placeholder="请输入内容 (默认值是19002)"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<footer class="anDiv">
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('网络配置', true)">下一步</el-button>
</footer>
</div>
<div class="network-box" v-if="tag==='网络配置'">
<el-form
ref="netForm"
:model="form"
:rules="netFormRules"
label-width="150px"
class="basic-form"
>
<el-row>
<el-col :span="20">
<el-form-item label="国家" prop="country_id">
<el-select v-model="form.country_id" placeholder="请选择国家" @change="getNetworkDict">
<el-option
v-for="item in countryDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="网络" prop="network_id">
<el-select v-model="form.network_id" placeholder="请选择网络地址">
<el-option
v-for="item in networkDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('基本配置')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('部署', true)">下一步</el-button>
</div>
</div>
</div>
<div class="deploy-box" v-if="tag==='部署'">
<div class="dmgxdjx">
<div style="margin-right: 45%">
<el-radio-group v-model="form.deployType" @change="selectDeploy">
<el-radio label="direct">直接部署</el-radio>
<el-radio label="only">仅添加配置</el-radio>
<el-radio label="start">启动采集程序并部署</el-radio>
</el-radio-group>
</div>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('网络配置')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="loading">确认</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ConfigForm',
props: {
isAdd: {
typeof: Boolean,
required: true
},
target_id: {
typeof: String,
required: true
},
otherRole: {
typeof: String,
required: true
},
countryDict: {
typeof: Array,
require: true
}
},
data() {
return {
visible:false,
loading: false,
tag:"基本配置",
setting_id: '',
form: {
role: 'da', // 角色选择
tor_version: '', // tor版本
image_id: '', // 选择镜像
replicas: null, // 复本数
bandwidth: null, // 宽带限制
memory: null, // 内存限制
service: '', // 服务器地址
out_port: null, // 代理端口
or_port: null, // or端口
dir_port: null, // dir端口
socks_port: null, // 客户端监听端口
control_port: null, // 客户端控制端口
country_id: '', // 国家
network_id: '', // 网络
deployType: 'direct', // 部署方式
direct: true, // 直接部署true 仅添加配置false
start_tcpdump: false // 启动采集程序并部署 true
},
roleDict:[
{label: '权威目录节点', value: 'da'},
{label: '路由节点', value: 'relay'},
{label: '出口节点', value: 'exit'},
{label: '洋葱服务节点', value: 'onion'},
{label: '客户端节点', value: 'client'},
{label: '入口节点', value: 'guard'},
{label: '其他节点', value: 'other'}
],
torDict: [],
networkDict: [],
outPortDict: [],
imageDict: [],
rules: {
role: [
{ required: true, message: '请选择角色', trigger: 'blur' }
],
tor_version: [
{ required: true, message: '请选择tor版本', trigger: 'blur' }
],
image_id: [
{ required: true, message: '请选择镜像', trigger: 'blur' }
],
service:[
{ required: true, message: '请输入服务器地址', trigger: 'blur' }
],
out_port: [
{ required: true, message: '请输入代理端口', trigger: 'blur' }
]
},
netFormRules: {
country_id: [
{ required: true, message: '请选择国家', trigger: 'blur' }
],
network_id: [
{ required: true, message: '请选择网络', trigger: 'blur' }
]
}
}
},
created() {
this.getOutPortDict()
},
methods: {
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.tag = '基本配置'
this.resetForm()
this.$emit('clearRoleForm')
},
// 重置
resetForm() {
this.form = {
role: 'da', // 角色选择
tor_version: '', // tor版本
image_id: '', // 选择镜像
replicas: '', // 复本数
bandwidth: '', // 宽带限制
memory: '', // 内存限制
service: '', // 服务器地址
out_port: '', // 代理端口
or_port: '', // or端口
dir_port: '', // dir端口
socks_port: '', // 客户端监听端口
control_port: '', // 客户端控制端口
country_id: '', // 国家
network_id: '', // 网络
deployType: 'direct', // 部署方式
direct: true, // 直接部署true 仅添加配置false
start_tcpdump: false // 启动采集程序并部署 true
}
},
// 选择哪个页面
updateTag(val, isValid){
if (val === '网络配置' && isValid) {
this.$refs.basicForm.validate((valid) => {
if (valid) {
this.tag=val
}
})
} else if(val === '部署' && isValid) {
this.$refs.netForm.validate((valid) => {
if (valid) {
this.tag=val
}
})
} else {
this.tag=val
}
},
// 角色选择
selectRole(node) {
if (node === 'other') {
this.$emit('openRoleForm')
}
// 获取torDict
this.getTorDict()
// 获取镜像字典
this.getImageDict()
},
// tor选择
selectTor(tor){
// 获取镜像字典
this.getImageDict()
},
// 获取torDick
getTorDict() {
this.$axios.get(this.$http.api.getTorDict, {role: this.form.role}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.torDict = res?.result
}
}).catch(err => {
console.log(err)
})
},
// 获取imageDict
getImageDict(){
const params = {
page:1,
size: 99,
image_name: this.form.role,
image_version: this.form.tor_version
}
if (this.form.role === 'other' && this.otherRole) {
params.image_name = this.otherRole
params.image_type = 'define'
}
this.$axios.get(this.$http.api.getImageDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.imageDict = res?.result?.items.map(item => {
return {
label: item.image_name + ':' + item.image_version,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
// 获取网络
getNetworkDict(){
const params = {
page:1,
size: 99,
country_id: this.form.country_id,
}
this.$axios.get(this.$http.api.getNetworkDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.networkDict = res?.result?.items.map(item => {
return {
label: item.cidr,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
showOutPortSelect() {
this.getOutPortDict()
},
// 获取代理端口字典
getOutPortDict(){
const params = {}
this.$axios.get(this.$http.api.getOutPortDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.outPortDict = res?.result
}
}).catch(err => {
console.log(err)
})
},
selectDeploy(deployType) {
switch (deployType) {
case 'direct':
this.form.direct = true
break
case 'only':
this.form.direct = false
break
case 'start':
this.form.start_tcpdump = true
break
default:
this.form.direct = true
break
}
},
// 提交数据
submit() {
const submitForm = this.setSubmitForm()
if (this.isAdd) {
this.add(submitForm)
} else {
this.edit(submitForm)
}
},
setSubmitForm() {
const {role, image_id } = this.form
let submitForm = {role, image_id }
if (this.form.replicas) {
submitForm.replicas = this.form.replicas
}
if (this.form.bandwidth) {
submitForm.bandwidth = this.form.bandwidth
}
if (this.form.memory) {
submitForm.memory = this.form.memory
}
if (this.form.role === 'other') {
submitForm.role = this.otherRole
} else if (this.form.role==='da'||this.form.role==='relay'||this.form.role ==='guard'||this.form.role ==='exit') {
submitForm.tor_version = this.form.tor_version
if (this.form.or_port) {
submitForm.or_port = this.form.or_port
}
if (this.form.dir_port) {
submitForm.dir_port = this.form.dir_port
}
} else if (this.form.role==='onion') {
// 服务器地址
submitForm.tor_version = this.form.tor_version
submitForm.service = this.form.service
if (this.form.socks_port) {
submitForm.socks_port = this.form.socks_port
}
if (this.form.control_port) {
submitForm.control_port = this.form.control_port
}
} else if (this.form.role==='client') {
// 代理端口
submitForm.tor_version = this.form.tor_version
submitForm.out_port = this.form.out_port
if (this.form.socks_port) {
submitForm.socks_port = this.form.socks_port
}
if (this.form.control_port) {
submitForm.control_port = this.form.control_port
}
}
return submitForm
},
add(submitForm) {
this.loading = true
const url = this.$http.api.setting + `/?target_id=${this.target_id}&network_id=${this.form.network_id}&direct=${this.form.direct}&start_tcpdump=${this.form.start_tcpdump}`
this.$axios.post(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
this.$notify({
title: '添加靶场配置成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
edit(submitForm) {
this.loading = true
const url = this.$http.api.setting+ `/${this.setting_id}/?target_id=${this.target_id}&network_id=${this.form.network_id}&direct=${this.form.direct}&start_tcpdump=${this.form.start_tcpdump}`
this.$axios.put(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
this.$notify({
title: '修改靶场配置成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang="less" scoped>
.config-dialog{
z-index: 997;
width: 620px;
height: 555px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../img/tjpz.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 7%;
padding-top: 3%
}
.basic-form {
margin-top: 20px;
text-align: left;
::v-deep .el-select {
width: 100%;
}
}
.tag{
margin-left: 9%;
.tags{
margin-right: 5%;
margin-top: 2%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.but-color {
background-color: #02DDEA !important;
}
.basic-box{
.anDiv{
width: 100%;
float: left;
margin-bottom: 10%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
.network-box{
.anDiv{
width: 100%;
float: left;
margin-top: 50%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
.deploy-box{
.dmgxdjx{
width: 100%;
float: left;
display: block;
margin-top: 3%;
margin-left: 20%;
text-align: left;
.el-radio {
display: block;
margin-top: 20%;
font-size: 14px;
color: #ffffff;
}
}
.anDiv{
width: 100%;
float: left;
margin-top: 49%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
}
</style>

View File

@@ -1,204 +0,0 @@
<template>
<div class="head">
<div class="target-select">
<el-select class="range-select" style="width:220px;" v-model="target_id" placeholder="全部靶场" @change="changeRange">
<el-option
v-for="item in rangeDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<img src="../../../img/btn/addConfigBtn.svg" style="height: 70% ;width: 10%;margin-right: 2%;color: #ffffff" @click="addConfig">
<div class="role-select">
<el-select v-model="role" clearable placeholder="节点角色" @change="query">
<el-option
v-for="item in roleDict"
:key="item.value"
:label="item.label"
:value="item.value">
<svg-icon :icon-class="item.value"></svg-icon>
<span style="margin-left: 10px;">{{ item.label }}</span>
</el-option>
</el-select>
</div>
<div class="deploy-select">
<el-select v-model="has_deployed" clearable placeholder="部署状态" @change="query">
<el-option
v-for="item in statusDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<el-button type="primary" :loading="batchDeployLoading" @click="batchDeploy">批量部署</el-button>
<el-button type="primary" :loading="batchDelLoading" @click="batchDel">批量删除</el-button>
<el-button type="primary" :loading="batchPipelineLoading" @click="batchPipeline" :disabled="disabledBatchPipline">批量运行流水线</el-button>
</div>
</template>
<script>
export default {
name: 'Header',
props: ['rangeDict', 'batchDeployLoading', 'batchDelLoading', 'batchPipelineLoading', 'disabledBatchPipline'],
data() {
return {
target_id: '',
role: '',
has_deployed: '',
roleDict:[
{label: '权威目录节点', value: 'da'},
{label: '路由节点', value: 'relay'},
{label: '出口节点', value: 'exit'},
{label: '洋葱服务节点', value: 'onion'},
{label: '客户端节点', value: 'client'},
{label: '入口节点', value: 'guard'}
],
statusDict: [
{value: 'true', label: '已部署'},
{value: 'false',label: '未部署'}
]
}
},
watch: {
'$store.state.range.targetId': {
handler(newVal, oldVal) {
this.target_id = newVal
},
immediate: true
}
},
methods: {
// 改变靶场
changeRange() {
this.$store.commit('range/setTargetId', this.target_id)
},
// 新增
addConfig() {
this.$emit('addConfig')
},
// 查询
query() {
let params = {}
if (this.has_deployed !== '') {
params.has_deployed = this.has_deployed
}
if (this.role !== '') {
params.role = this.role
}
this.$emit('query', params)
},
// 批量部署
batchDeploy() {
this.$emit('batchDeploy')
},
// 批量删除
batchDel() {
this.$emit('batchDel')
},
// 批量运行流水线
batchPipeline() {
this.$emit('batchPipeline')
}
}
}
</script>
<style lang="less" scoped>
.head{
width: 95%;
height: 7%;
margin-top: 1%;
margin-left: 2.5%;
text-align: right;
/*background-color: #5daf34;*/
.block{
display: inline-block;
margin-left: 2%;
}
.input{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
.target-select{
font-size: 10px;
float: left;
margin-top: 0.5%;
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.custom-popper .el-select-dropdown {
max-height: 3px;
}
.range-select {
::v-deep .el-input {
width: 80%;
.el-input__inner {
background-color: transparent !important;
border-color: transparent;
border-radius: 0;
color: #FFFFFF;
}
/* select去除竖线 */
.el-input__suffix::before {
content: "";
width: 0;
height: 0;
margin: 0;
position: absolute;
}
/*select的上下箭头图标样式*/
.el-select__caret {
color: rgba(2, 221, 234, 0.9);
}
}
}
}
.deploy-select{
display: inline-block;
height: 60%;
width: 10%;
margin-top: 0.5%;
margin-left: 0.5%;
margin-right: 1%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
::v-deep .role-select{
display: inline-block;
}
}
</style>

View File

@@ -1,94 +0,0 @@
<template>
<div class="role-dialog" v-if="visible">
<i class="el-icon-close" style="float: right; padding-right: 7%;padding-top: 2.8%" @click="close"></i>
<div class="fbs">
<span style="margin-right: 4.5%">角色</span>
<el-input
:value="value"
placeholder="请输入内容"
style="width: 50%"
v-bind="$attrs"
v-on="$listeners">
</el-input>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="submit">确认</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RoleForm',
props: {
value: {
typeof: String,
require: true,
default: ''
}
},
data() {
return {
visible: false
}
},
watch: {
value: {
handler(val) {
this.$emit('input', val)
}
}
},
methods: {
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
},
submit() {
this.$emit('submitOtherRole')
document.querySelector('.mask').style.display = 'none'
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.role-dialog{
z-index: 998;
width: 30%;
height: 20%;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../img/jbpzxybqr.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.fbs{
width: 100%;
float: left;
margin-top: 3%;
text-align: center;
}
.anDiv{
width: 100%;
float: left;
margin-top: 10%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 10%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
</style>

View File

@@ -1,420 +0,0 @@
<template>
<div class="range-manage" ref="appRef">
<div class="show">
<Header
@quickAdd="quickAdd"
@customAdd="customAdd"
@query="query"
></Header>
<div class="list" id="show-loading">
<div class="single" v-for="rangeItem in rangeList" :key="rangeItem.target_name">
<div class="title">
<span style="font-size: 20px;">{{rangeItem.target_name+' '+getDeployStatus(rangeItem,'deploy_status')}}</span>
<el-button :class="rangeItem.attribute==='public' ? 'gOsY' : 'gOsN'" >{{rangeItem.attribute==='public' ? '公用' : '私有'}}</el-button>
</div>
<div class="container">
<div class="row">
<div class="cell">{{`正常节点:${getDeployStatus(rangeItem,'deploy_success_count')}`}}</div>
<div class="cell">{{`异常节点:${getDeployStatus(rangeItem,'deploy_default_count')}`}}</div>
<div class="cell">{{`节点数量:${getDeployStatus(rangeItem,'deploy_total_count')}`}}</div>
</div>
<div class="row">
<div class="cell small">{{`创建人:${rangeItem.username}`}}</div>
<div class="cell large">{{`创建时间:${formatTime(rangeItem.create_time)}`}}</div>
</div>
</div>
<div class="buttons">
<el-button class="glBut" type="primary" :disabled="rangeItem.delLoading" @click="edit(rangeItem)">修改</el-button>
<el-button class="glBut" type="primary" :loading="rangeItem.delLoading" @click="del(rangeItem)">删除</el-button>
<el-button class="glBut" type="primary" @click="goConfigManage(rangeItem.id)">配置管理</el-button>
<el-button class="glBut" type="primary" @click="goNodeManage(rangeItem.id)">节点管理</el-button>
</div>
</div>
</div>
<el-pagination
background
:current-page="page"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
<div class="mask"></div>
<QuickCreate ref="quickCreate" :is-add="isAdd" @refresh="init"></QuickCreate>
<CustomCreate ref="customCreate" :is-add="isAdd" @refresh="init"></CustomCreate>
</div>
</div>
</template>
<script>
import Header from './module/Header.vue'
import QuickCreate from './module/QuickCreate.vue'
import CustomCreate from './module/CustomCreate.vue'
import { formatTime } from '../../utils'
import { Loading } from 'element-ui'
import { getTargetsResponse } from './mock.js'
export default {
name: "RangeMange",
components:{
Header,
QuickCreate,
CustomCreate
},
// props:['fTag','fInput','fFrom','FIsConfigQuery'],
data(){
return{
page: 1,
size: 10,
total: 0,
isAdd: false,
rangeList:[],
delTimer: null,
pendingTimer: null,
counter: 0
}
},
computed: {},
watch: {},
filters: {},
created() {
// this.init()
},
mounted() {
this.init()
},
destroyed() {
if (this.pendingTimer) {
clearInterval(this.pendingTimer)
this.pendingTimer = null
}
if(this.delTimer) {
clearInterval(this.delTimer)
this.delTimer = null
}
this.counter = 0
},
methods:{
formatTime,
// 初始化数据
init(params={}) {
// this.rangeList = getTargetsResponse?.result?.items
// this.total = getTargetsResponse?.result?.total
const reqParams = {
page: this.page,
size: this.size,
...params
}
// TODO: 网络请求先注释,后期放开
let loadingInstance = Loading.service({ text: '加载中...', target: '#show-loading'}) // 开启loading
this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.total = res?.result?.total
this.rangeList = res?.result?.items
this.rangeList.map(item => {
this.$set(item, 'delLoading', false)
return item
})
let index = this.rangeList?.findIndex(item => {
return item.target_deploy_statuses[0].deploy_status === '部署中' || item.target_deploy_statuses[0].deploy_status === '删除中'
})
if (index !== -1) {
if (!this.pendingTimer) {
this.pendingTimer = setInterval(() => {
this.timerUpdateList(params)
}, 10 * 1000)
}
}
}
}).catch(err => {
console.log(err)
}).finally(() => {
loadingInstance.close()
if(this.rangeList.length === 0) {
this.$store.commit('range/setTargetId', '')
}
})
},
// 查询
query(params) {
this.init(params)
},
// pendingTimer只刷新数据
timerUpdateList(params) {
const reqParams = {
page: this.page,
size: this.size,
...params
}
this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.total = res?.result?.total
this.rangeList = res?.result?.items
this.rangeList.map(item => {
this.$set(item, 'delLoading', false)
return item
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
if(this.rangeList.length === 0) {
this.$store.commit('range/setTargetId', '')
}
})
},
// 修改
edit(rangeItem) {
if (rangeItem.create_mode === 'define') {
// 自定义靶场修改
this.isAdd = false
this.$refs.customCreate.target_id = rangeItem.id
this.$refs.customCreate.user_id = rangeItem.user_id
this.$refs.customCreate.form.target_name = rangeItem.target_name
this.$refs.customCreate.form.description = rangeItem.description
this.$refs.customCreate.form.attribute = rangeItem.attribute
document.querySelector('.mask').style.display = 'block'
this.$refs.customCreate.visible = true
} else {
// 快速创建靶场修改
this.isAdd = false
this.$refs.quickCreate.target_id = rangeItem.id
this.$refs.quickCreate.user_id = rangeItem.user_id
this.$refs.quickCreate.form.target_name = rangeItem.target_name
this.$refs.quickCreate.form.description = rangeItem.description
if (rangeItem.ratio === 0.01 || rangeItem.ratio === 0.005 || rangeItem.ratio === 0.002 || rangeItem.ratio === 0.001) {
this.$refs.quickCreate.form.ratio = rangeItem.ratio.toString()
} else {
this.$refs.quickCreate.otherRatio = rangeItem.ratio.toString()
}
this.$refs.quickCreate.form.attribute = rangeItem.attribute
this.$refs.quickCreate.form.createType = rangeItem.create_mode
document.querySelector('.mask').style.display = 'block'
this.$refs.quickCreate.visible = true
}
},
// 删除
del(rangeItem) {
rangeItem.delLoading = true
const url = this.$http.api.asyncTarget + '/' + rangeItem.id
this.$axios.delete(url, {}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
}, 10 * 1000)
// this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
// rangeItem.delLoading = false
})
},
// 获取任务进度
getTask(task_id) {
const url = this.$http.api.task + '/' + task_id
this.$axios.get(url, {}).then(res => {
if(res.task_status !== 'PENDING' && res.task_status !== 'STARTED') {
if (res?.task_result?.code === 500 || res?.task_result?.message === 'false') {
this.$notify({
title: `${res.task_id}任务执行完毕,删除靶场失败`,
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: `${res.task_id}任务执行完毕,删除靶场成功`,
type: 'success',
duration: 2500
})
}
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.init()
} else {
// this.$notify({
// title: `${res.task_id}任务执行中`,
// type: 'success',
// duration: 2500
// })
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.counter++
if (this.counter >= 60) {
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.init()
}
})
},
// 配置管理页面
goConfigManage(id) {
this.$store.commit('globalAttrs/setCheckMenu', 'rangeConfigManage')
this.$store.commit('range/setTargetId', id)
},
// 节点管理页面
goNodeManage(id) {
this.$store.commit('globalAttrs/setCheckMenu', 'rangeNodeManage')
this.$store.commit('range/setTargetId', id)
},
// 打开快速创建靶场
quickAdd() {
this.isAdd = true
document.querySelector('.mask').style.display = 'block'
this.$refs.quickCreate.visible = true
},
// 打开自定义创建靶场
customAdd() {
this.isAdd = true
document.querySelector('.mask').style.display = 'block'
this.$refs.customCreate.visible = true
},
getDeployStatus(rangeItem, deploy) {
if (rangeItem.target_deploy_statuses.length > 0) {
return rangeItem.target_deploy_statuses[0][deploy]
} else {
return '--'
}
},
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.page=1
this.size=val
this.query()
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.page=val
this.query()
}
}
}
</script>
<style lang='less' scoped>
.range-manage{
width: 100%;
height: 100%;
float: right;
position: relative; /* 确保相对定位生效 */
.show{
width: 95%;
height: 95%;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../img/backgroundFourCorner.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.list{
display:flex;
flex-direction: row;
justify-content: flex-start;
flex-wrap: wrap;
align-content: flex-start;
width: 95%;
height: 84%;
margin-left: 2.5%;
.single:hover{
border: 1px solid #159dd3;
}
.single{
width: 32%;
height: 23.5%;
border-radius: 8px;
border: 1px solid rgba(186, 208, 241, 0.10);
background: rgba(25, 33, 61, 0.40);
margin-left: 1.2%;
margin-bottom: 0.9%;
padding: 20px;
display: inline-block;
.title{
text-align: left;
.gOsY{
width: 15px;
height: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(121, 175, 122, 0.3);
color: #36af04;
}
.gOsN{
width: 15px;
height: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(207, 172, 93, 0.3);
color: #af8109;
}
}
.container{
width: 100%;
display: flex;
flex-direction: column;
margin: 10px 0 10px 0;
.row {
display: flex;
padding-bottom: 10px;
}
.cell {
flex-grow: 1;
color: rgba(255, 255, 255, 0.6);
border: none;
text-align: left;
}
.cell.small {
flex-basis: 33.5%;
}
.cell.large {
flex-basis: 66.5%;
}
}
.buttons{
text-align: left;
.glBut{
width: 80px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
// margin-left: 2%;
background-color: rgba(14, 61, 138, 0.50);
color: #02DDEA;
}
}
}
}
// 遮罩层
.mask{
position: fixed; /*将元素设置为固定定位*/
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /*通过rgba函数来控制遮罩层的透明度*/
display: none; /*将元素隐藏*/
}
}
}
</style>

View File

@@ -1,90 +0,0 @@
const getTargetsResponse = {
"code":200,
"message":"success",
"result":{
"items":[
{
"target_name":"tor测试靶场",
"description":"部署tor网络",
"attribute":"public",
"id":23,
"user_id":2,
"create_time":"2023-12-07T14:24:53",
"target_deploy_statuses":[
{
"deploy_status":"部署成功",
"deploy_total_count":20,
"deploy_success_count":18,
"deploy_default_count":2
}
]
},
{
"target_name":"tor测试靶场2",
"description":"部署tor网络",
"attribute":"public",
"id":24,
"user_id":2,
"create_time":"2023-12-07T14:37:54",
"target_deploy_statuses":[
{
"deploy_status":"未部署",
"deploy_total_count":0,
"deploy_success_count":0,
"deploy_default_count":0
}
]
},
{
"target_name":"string",
"description":"string",
"attribute":"public",
"id":34,
"user_id":4,
"create_time":"2023-12-13T00:30:22",
"target_deploy_statuses":[
]
},
{
"target_name":"XT-RANGE1",
"description":"XT测试靶场1",
"attribute":"public",
"id":35,
"user_id":4,
"create_time":"2023-12-13T10:57:05",
"target_deploy_statuses":[
]
},
{
"target_name":"XT-RANGE2",
"description":"XT测试靶场2",
"attribute":"public",
"id":36,
"user_id":4,
"create_time":"2023-12-13T15:55:12",
"target_deploy_statuses":[
]
},
{
"target_name":"XT-RANGE3",
"description":"XT测试靶场3",
"attribute":"public",
"id":37,
"user_id":4,
"create_time":"2023-12-13T15:55:12",
"target_deploy_statuses":[
]
}
],
"total":6,
"page":1,
"size":50,
"pages":1
}
}
export { getTargetsResponse }

View File

@@ -1,183 +0,0 @@
<template>
<div
class="custom-dialog"
v-if="visible"
>
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" style="float: right; padding-right: 8%;padding-top: 3%" @click="close"></i>
<el-form
ref="customForm"
:model="form"
:rules="rules"
label-width="150px"
class="custom-form"
>
<el-row>
<el-col :span="20">
<el-form-item label="靶场名称" prop="target_name">
<el-input v-model="form.target_name" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="靶场描述" prop="description">
<el-input type="textarea" v-model="form.description" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="所有权" prop="attribute">
<el-radio v-model="form.attribute" label="private">私有</el-radio>
<el-radio v-model="form.attribute" label="public">公用</el-radio>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="submit-footer">
<div>
<el-button class="glBut" type="primary" @click="resetForm">重置</el-button>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="loading">{{isAdd ? '提交' : '确定'}}</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CustomCreate',
props: {
isAdd: {
typeof: Boolean,
required: true
}
},
data() {
return {
visible: false,
loading: false,
form: {
target_name: '', // 靶场名称
description: '', // 靶场描述
attribute: '' // 所有权
},
target_id: '',
user_id: '',
rules: {
target_name: [
{ required: true, message: '请输入靶场名称', trigger: 'blur' }
],
description: [
{ required: false, message: '请输入描述', trigger: 'blur' }
],
attribute: [
{ required: true, message: '请选择所有权', trigger: 'change' }
]
}
}
},
methods: {
close() {
this.resetForm()
document.querySelector('.mask').style.display = 'none'
this.visible = false
},
submit() {
this.$refs.customForm.validate((valid) => {
if (valid) {
if (this.isAdd) {
this.add()
} else {
this.edit()
}
}
})
},
add () {
this.loading = true
const url = this.$http.api.target
this.$axios.post(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '创建靶场成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
edit() {
this.loading = true
const url = this.$http.api.target + `/${this.target_id}`
this.$axios.put(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '编辑靶场成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
resetForm() {
this.form = {
target_name: '', // 靶场名称
description: '', // 靶场描述
attribute: '' // 所有权
}
}
}
}
</script>
<style lang="less" scoped>
.custom-dialog{
width: 520px;
height: 363px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../img/Group.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.custom-form {
margin-top: 70px;
text-align: left;
}
.submit-footer{
width: 100%;
float: left;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
.but-color {
background-color: #02DDEA;
}
}
}
</style>

View File

@@ -1,161 +0,0 @@
<template>
<div class="head">
<span style="font-size: 14px;float: left;padding-top: 1%">靶场列表</span>
<img src="../../../img/btn/quickCreateRangeBtn.svg" style="height: 70% ;width: 10%;margin-right: 7%;color: #ffffff" @click="quickAdd">
<img src="../../../img/btn/customCreateRangeBtn.svg" style="height: 70% ;width: 10%;margin-right: 7%;color: #ffffff" @click="customAdd">
<div class="input">
<el-input v-model="target_name" placeholder="名称查询输入" suffix icon="">
<template v-slot:suffix>
<div class="icon-group">
<img src="../../../img/inputl.png" alt="**">
<img src="../../../img/inputIcon.png" alt="*" @click="query">
</div>
</template>
</el-input>
</div>
<div class="state">
<el-select v-model="deploy_status" placeholder="全部状态" clearable @change="query">
<el-option
v-for="item in statusDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="project">
<el-select v-model="attribute" placeholder="全部工程" clearable @change="query">
<el-option
v-for="item in attributeDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</div>
</template>
<script>
export default {
data () {
return {
target_name: '', // 靶场名称
deploy_status: '',
attribute: '',
statusDict: [
{
value: '部署成功',
label: '部署成功'
},
{
value: '部署中',
label: '部署中'
},
{
value: '未部署',
label: '未部署'
},
{
value: '部署失败',
label: '部署失败'
}
],
attributeDict: [
{
value: 'public',
label: '公用'
},
{
value: 'private',
label: '私有'
}
],
}
},
methods: {
quickAdd() {
this.$emit('quickAdd')
},
customAdd() {
this.$emit('customAdd')
},
query() {
const params = {
target_name: this.target_name,
deploy_status: this.deploy_status,
attribute: this.attribute
}
this.$emit('query', params)
}
}
}
</script>
<style lang="less" scoped>
.head{
width: 95%;
height: 7%;
margin-top: 1%;
margin-left: 2.5%;
text-align: right;
.block{
display: inline-block;
margin-left: 2%;
}
.input{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
.state{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.custom-popper .el-select-dropdown {
max-height: 3px;
}
}
.project{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
margin-right: 3%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
}
</style>

View File

@@ -1,437 +0,0 @@
<template>
<div class="quick-dialog" v-if="visible">
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" style="float: right; padding-right: 8%;padding-top: 3%" @click="close"></i>
<el-form
ref="quickForm"
:model="form"
:rules="rules"
label-width="150px"
class="quick-form"
>
<el-row>
<el-col :span="20">
<el-form-item label="靶场名称" prop="target_name">
<el-input v-model="form.target_name" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="靶场描述" prop="description">
<el-input type="textarea" v-model="form.description" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="选择按真实tor网络模拟比例" class="long-item-label">
<el-radio-group v-model="form.ratio" @change="radioChange">
<el-radio label="0.01">1:100</el-radio>
<el-radio label="0.02">1:200</el-radio>
<el-radio label="0.002">1:500</el-radio>
<el-radio label="0.001">1:1000</el-radio>
</el-radio-group>
<div style="margin-top: 2%">
<span style="font-size: 12px">其他</span>
<el-input v-model="otherRatio" class="other-ratio" placeholder="请输入内容" @focus="otherRadioInput"></el-input>
<span style="font-size: 14px;color: #4b5362">(范围在0.001-0.01之间)</span>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="所有权" prop="attribute">
<el-radio v-model="form.attribute" label="private">私有</el-radio>
<el-radio v-model="form.attribute" label="public">公用</el-radio>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="创建方式" prop="createType">
<el-radio v-model="form.createType" label="interface">接口模式</el-radio>
<el-radio v-model="form.createType" label="consensus">consensus模式</el-radio>
</el-form-item>
</el-col>
</el-row>
<el-row>
<div class="upload-file" v-if="form.createType==='consensus'">
<input class="uploadBgImg" type="file" name="file" @change="selectFile" />
<div v-if="fileName">{{ fileName }}</div>
</div>
</el-row>
</el-form>
<div class="submit-footer" :style="form.createType==='consensus'?'margin-top: 60px':'margin-top: 120px'">
<div>
<el-button class="glBut" type="primary" @click="resetForm">重置</el-button>
<el-button class="glBut but-color" style="width: 120px;" type="primary" @click="submitConfig" :loading="configLoading">仅添加配置</el-button>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="createLoading">创建</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'QuickCreate',
props: {
isAdd: {
typeof: Boolean,
required: true
}
},
data() {
return {
visible: false,
createLoading: false,
configLoading: false,
form: {
target_name: '', // 靶场名称
description: '', // 靶场描述
ratio: '', // 选择按真实tor网络模拟的比例
attribute: '', // 所有权
createType: '', // 创建方式
file: '' // 上传文件
},
target_id: '',
user_id: '',
fileName: '',
otherRatio: '',
delTimer: null,
counter: 0,
rules: {
target_name: [
{ required: true, message: '请输入靶场名称', trigger: 'blur' }
],
description: [
{ required: false, message: '请输入描述', trigger: 'blur' }
],
ratio: [
{ required: true, message: '请选择模拟比例', trigger: 'change' }
],
attribute: [
{ required: true, message: '请选择所有权', trigger: 'change' }
],
createType: [
{ required: true, message: '请选择创建方式', trigger: 'change' }
]
}
}
},
destroyed() {
if(this.delTimer) {
clearInterval(this.delTimer)
this.delTimer = null
}
this.counter = 0
},
methods: {
close() {
this.resetForm()
document.querySelector('.mask').style.display = 'none'
this.visible = false
},
// 重置
resetForm() {
this.fileName = ''
this.otherRatio = ''
this.form = {
target_name: '', // 靶场名称
description: '', // 靶场描述
ratio: '', // 选择按真实tor网络模拟的比例
attribute: '', // 所有权
createType: '', // 创建方式
file: '' // 上传文件
}
},
// 仅添加配置
submitConfig() {
this.$refs.quickForm.validate((valid) => {
if (valid) {
this.configLoading = true
if (this.isAdd) {
this.add(false)
} else {
this.edit(false)
}
}
})
},
// 创建
submit() {
this.$refs.quickForm.validate((valid) => {
if (valid) {
this.createLoading = true
if (this.isAdd) {
this.add(true)
} else {
this.edit(true)
}
}
})
},
add (isCreate) {
if (this.form.createType === 'interface') {
// 接口模式
const url = isCreate ? this.$http.api.asyncQuickInterface + '/?direct=true' : this.$http.api.asyncQuickInterface + '/?direct=false'
const {target_name, description, ratio, attribute} = this.form
const submitForm = { target_name, description, ratio, attribute }
this.$axios.postForm(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
// this.$notify({
// title: '创建靶场成功',
// type: 'success',
// duration: 2500
// })
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
}, 10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.createLoading = false
this.configLoading = false
})
} else {
// consensus模式
const url = isCreate ? this.$http.api.asyncQuickConsensus + '/?direct=true' : this.$http.api.asyncQuickConsensus + '/?direct=false'
const submitForm = new FormData()
for(const key in this.form) {
submitForm.append(key, this.form[key])
}
this.$axios.postFormData(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
// this.$notify({
// title: '创建靶场成功',
// type: 'success',
// duration: 2500
// })
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
},10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.createLoading = false
this.configLoading = false
})
}
},
edit(isCreate) {
const editForm = JSON.parse(JSON.stringify(this.form))
if (this.otherRatio !== '') {
editForm.ratio = this.otherRatio
}
if(this.form.createType === 'interface') {
// 接口模式
const url = isCreate ? this.$http.api.asyncQuickInterface + `/${this.target_id}/?direct=true` : this.$http.api.asyncQuickInterface + `/${this.target_id}/?direct=false`
this.$axios.putForm(url, editForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
// this.$notify({
// title: '编辑靶场成功',
// type: 'success',
// duration: 2500
// })
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
}, 10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.createLoading = false
this.configLoading = false
})
} else {
// consensus模式
const url = isCreate ? this.$http.api.asyncQuickConsensus + `/${this.target_id}/?direct=true` : this.$http.api.asyncQuickConsensus + `/${this.target_id}/?direct=false`
const submitForm = new FormData()
for(const key in editForm) {
submitForm.append(key, editForm[key])
}
this.$axios.putFormData(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
// this.$notify({
// title: '编辑靶场成功',
// type: 'success',
// duration: 2500
// })
this.$notify({
title: res.message,
type: 'success',
duration: 2500
})
this.delTimer = setInterval(() => {
this.getTask(res?.result?.task_id || '')
},10 * 1000)
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.createLoading = false
this.configLoading = false
})
}
},
// 模拟比例选择
radioChange(val) {
this.otherRatio = ''
},
// 模拟比例用输入框输入
otherRadioInput(val) {
this.form.ratio = ''
},
// 选择上传文件
selectFile(e) {
this.form.file = e.target.files[0]
this.fileName = e.target.files[0].name
},
// 获取任务进度
getTask(task_id) {
const url = this.$http.api.task + '/' + task_id
this.$axios.get(url, {}).then(res => {
if(res.task_status !== 'PENDING' && res.task_status !== 'STARTED') {
if (res?.task_result?.code === 500 || res?.task_result?.message === 'false') {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行完毕,靶场部署失败`,
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: res?.task_result?.result || `${res.task_id}任务执行完毕,靶场部署成功`,
type: 'success',
duration: 2500
})
}
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.$emit('refresh')
} else {
// this.$notify({
// title: `${res.task_id}任务执行中`,
// type: 'success',
// duration: 2500
// })
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.counter++
if (this.counter >= 60) {
clearInterval(this.delTimer)
this.delTimer = null
this.counter = 0
this.$emit('refresh')
}
})
},
}
}
</script>
<style lang="less" scoped>
.quick-dialog{
width: 620px;
height: 675px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../img/Group (2).svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.quick-form {
margin-top: 70px;
text-align: left;
.long-item-label {
::v-deep .el-form-item__label {
padding: 12px 12px 0 70px;
}
}
.other-ratio{
width: 40%;
margin-left: 5%;
margin-right: 5%;
background-color: #0c295b;
display: inline-block;
border: none;
}
}
.upload-file{
width: 100%;
/*margin-left: 10%;*/
text-align: center;
color: rgba(81, 84, 102, 0.84);
::v-deep .el-upload-list {
margin: 0;
list-style: none;
width: 400px !important;
padding-left: 20%;
}
.uploadBgImg{
width: 320px;
height: 40px;
background-image: url("../../../img/shangchuan.png");
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% auto; /* 宽度为100%,高度自适应保持宽高比 */
text-align: right;
padding-right: 10%;
padding-top: 2%;
font-size: 0;
}
.uploadBgImg::file-selector-button{
padding: 0;
background-color: transparent;
cursor: pointer;
font-size: 0;
}
}
.submit-footer{
width: 100%;
float: left;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
.but-color {
background-color: #02DDEA;
}
}
}
</style>

View File

@@ -1,220 +0,0 @@
<template>
<div :class="['tabs', {'is--maximize': isFullscreen}]">
<el-tabs
v-model="activeName"
@tab-click="handleClick"
>
<el-tab-pane label="控制台" name="first">
<ControlConsole ref="controlConsole"></ControlConsole>
</el-tab-pane>
<el-tab-pane label="文件管理" name="second">
<FileManage :is-fullscreen="isFullscreen"></FileManage>
</el-tab-pane>
</el-tabs>
<el-button class="console-btn" v-if="activeName === 'first'" :loading="consoleLoading" type="primary" @click="openConsoleTab">打开控制台</el-button>
<span class="icon-span" slot="label">
<i v-if="closeIcon" class="el-icon-close icon-zoom" @click="backNodeList"></i>
<svg-icon v-else class="icon-zoom" :icon-class="fullscreenIcon" @click="zoomEvent"></svg-icon>
</span>
</div>
</template>
<script>
import ControlConsole from './module/ControlConsole'
import FileManage from './module/FileManage'
export default {
components: { ControlConsole, FileManage },
data() {
return {
activeName: 'first',
nodeId: '',
// consoleUrl: '',
fullscreenIcon: 'fullscreen',
isFullscreen: false,
closeIcon: false,
consoleLoading: false
};
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
// this.init()
},
immediate: true
}
},
mounted() {
this.closeIcon = this.$route.params.closeIcon
},
methods: {
backNodeList() {
this.$router.push({ name: 'rangeNodeManage' })
},
handleClick(tab, event) {
console.log(tab, event);
},
openConsoleTab() {
// 获取webshell地址用浏览器打开
const reqParams = { node_id: this.nodeId }
this.consoleLoading = true
this.$axios.get(this.$http.api.getWebshell, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const consoleUrl = 'http://' + window.g.baseURL + '/' + res?.result
window.open(consoleUrl, '_blank')
// var newWindow = window.open(consoleUrl , '_blank');
// // 等待新页面加载完成
// newWindow.onload = function() {
// console.log('1111111111111111')
// // 选择用户名输入框
// var usernameInput = newWindow.document.querySelector('input[name="username"]');
// // 选择密码输入框
// var passwordInput = newWindow.document.querySelector('input[name="password"]');
// // 选择登录按钮
// var loginButton = newWindow.document.querySelector('button[type="submit"]');
// console.log(usernameInput, 'usernameInput====')
// // 如果输入框存在,则自动输入用户名和密码
// if (usernameInput && passwordInput && loginButton) {
// usernameInput.value = 'admin';
// passwordInput.value = 'Bjhit@2020';
// // 触发登录按钮的点击事件
// loginButton.click();
// }
// };
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.consoleLoading = false
})
},
zoomEvent() {
if (this.activeName === 'first') {
this.isFullscreen = !this.isFullscreen
this.fullscreenIcon = this.isFullscreen ? 'narrow' : 'fullscreen'
// this.$refs.controlConsole.resize()
} else {
this.isFullscreen = !this.isFullscreen
this.fullscreenIcon = this.isFullscreen ? 'narrow' : 'fullscreen'
}
},
findNthOccurrence(str, char, n) {
let index = str.indexOf(char)
while (--n > 0 && index !== -1) {
index = str.indexOf(char, index + 1)
}
return index
}
}
}
</script>
<style lang='less' scoped="scoped">
.is--maximize {
position: fixed !important;
top: 9%;
left: 10%;
width: 90% !important;
height: 91% !important;
padding: 0.5em 1em;
// background-color: #17234e;
background: url(../../../../img/background/bgMain.svg);
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-position: center; /* 居中显示 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
z-index: 1000;
}
// ::v-deep #tab-zoom {
// border: none;
// background-color: #1b202e !important;
// position: absolute;
// top: 1px;
// right: 0;
// font-weight: 500;
// font-size: 15px;
// color: #fff !important;
// }
.tabs {
width: 100%;
height: calc(100% - 15px);
text-align: center;
position: relative;
padding: 0 0;
// display: flex;
// .detail-title {
// position:absolute;
// margin: 10px;
// }
}
::v-deep .el-tabs{
color: #000;
left: 0px;
top: 0px;
padding: 0 0;
width: 100%;
height: 100%;
position: relative;
.el-tabs__header {
margin: 15px 30px;
}
.el-tabs__content {
width: 100%;
height: calc(100% - 50px);
.el-tab-pane {
width: 100%;
height: 100%;
}
}
}
/* 去除灰色横条 */
::v-deep .el-tabs__nav-wrap::after {
position: static !important;
}
/* 设置滑块颜色 */
::v-deep .el-tabs__active-bar{
background-color: #0E3D8A !important;
}
/* 设置滑块停止位置 */
::v-deep .el-tabs__active-bar.is-top{
height: 37px;
width: 104px ! important;
border-radius: 17px;
top: 0px !important;
left: -18px !important;
position: absolute !important;
z-index: 1;
}
/* 设置当前选中样式 */
::v-deep .el-tabs__item.is-active{
color:#02DDEA !important;
z-index: 2;
}
/* 设置未被选中样式 */
::v-deep .el-tabs__item{
padding: 0 20px !important;
width: 104px;
box-sizing: border-box;
display: inline-block;
position: relative !important;
color:#02DDEA !important;
z-index: 2;
}
.console-btn {
position: absolute;
top: 5px;
right: 100px;
}
.icon-span {
position: absolute;
top: 10px;
right: 30px;
.icon-zoom {
font-size: 30px;
color: #02DDEA;
margin: 0 15px;
}
}
</style>

View File

@@ -1,389 +0,0 @@
<template>
<div
style="height: 100%;
background: #002833;"
>
<div class="indexContainer" id="terminal" ref="terminal" v-resize="onResize"></div>
</div>
</template>
<script>
// 引入xterm请注意这里和3.x版本的引入路径不一样
import { Terminal } from "xterm";
import "xterm/css/xterm.css";
import "xterm/lib/xterm.js";
import { FitAddon } from "xterm-addon-fit";
import resize from 'vue-resize-directive';
export default {
name: "Shell",
directives: { resize },
data() {
return {
nodeId: '',
showOrder: "", // 保存服务端返回的命令
inputList: [],
shellWs: "",
term: "", // 保存terminal实例
rows: 40,
cols: 100,
urlParam: {
Tag: 'tag',
name: 'name',
pod: 'pod'
},
commandPrefix: 'target'
};
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
},
immediate: true
}
},
created() {
this.getWebsocketUrl()
},
mounted() {
let _this = this;
localStorage.setItem('commands', '')
// 获取容器宽高/字号大小,定义行数和列数
this.rows = document.querySelector(".indexContainer").offsetHeight / 16 - 6;
this.cols = document.querySelector(".indexContainer").offsetWidth / 14;
let term = new Terminal({
rendererType: "canvas", //渲染类型
rows: parseInt(_this.rows), //行数
cols: parseInt(_this.cols), // 不指定行数,自动回车后光标从下一行开始
convertEol: true, //启用时,光标将设置为下一行的开头
fontSize: 16, //字体大小
// scrollback: 50, //终端中的回滚量
disableStdin: false, //是否应禁用输入。
cursorStyle: "underline", //光标样式
cursorBlink: true, //光标闪烁
// theme: {
// foreground: "#7e9192", //字体
// background: "#002833", //背景色
// cursor: "help", //设置光标
// lineHeight: 16
// }
theme: {
foreground: "yellow", //字体
background: "#060101", //背景色
cursor: "help", //设置光标
lineHeight: 16
}
});
// 创建terminal实例
term.open(this.$refs["terminal"]);
// 换行并输入起始符“$”
term.prompt = () => {
term.write("\r\n$ ");
};
term.prompt();
// // canvas背景全屏
var fitAddon = new FitAddon();
term.loadAddon(fitAddon);
fitAddon.fit();
window.addEventListener("resize", resizeScreen);
// document.querySelector(".indexContainer").addEventListener('resize', resizeScreen)
// 内容全屏显示
function resizeScreen() {
// 不传size
try {
fitAddon.fit();
// 窗口大小改变时触发xterm的resize方法向后端发送行列数格式由后端决定
// 这里不使用size默认参数因为改变窗口大小只会改变size中的列数而不能改变行数所以这里不使用size.clos,而直接使用获取我们根据窗口大小计算出来的行列数
term.onResize(() => {
_this.onSend({ Op: "resize", Cols: term.cols, Rows: term.rows });
});
} catch (e) {
console.log("e", e.message);
}
}
function runFakeTerminal(_this) {
if (term._initialized) {
return;
}
term._initialized = true;
term.prompt = () => {
term.write("\r\n ");
};
// term.writeln("Welcome to xterm.js");
// term.writeln(
// "This is a local terminal emulation, without a real terminal in the back-end."
// );
// term.writeln("Type some keys and commands to play around.");
// term.writeln("root@target:/#");
// term.prompt();
// 监控键盘输入事件
// / **
// *添加事件监听器,用于按下键时的事件。事件值包含
// *将在data事件以及DOM事件中发送的字符串
// *触发了它。
// * @返回一个IDisposable停止监听。
// * /
let last = 0;
term.onKey(function(event) {
// 可打印状态即不是alt键ctrl等功能健时
// console.log(event, '输入的key======')
const printable = !event.domEvent.altKey && !event.domEvent.altGraphKey && !event.domEvent.ctrlKey && !event.domEvent.metaKey
// !key.charCodeAt(0).altKey && !key.charCodeAt(0).altGraphKey && !key.charCodeAt(0).ctrlKey && !key.charCodeAt(0).metaKey;
// 因服务端返回命令包含乱码但使用write方法输出时并不显示故将真实显示内容截取出来
let show = ''
if (_this.showOrder) {
let index = _this.showOrder.indexOf("sh");
show = _this.showOrder.substr(index, _this.showOrder.length - 1);
}
// 当输入回车时
if (event.domEvent.keyCode === 13) {
if (_this.order == "cls" || _this.order == "clear") {
_this.order = "";
return false;
}
//先将数据发送
term.prompt();
// 判断如果不是英文给出提醒
let reg = /[a-zA-Z]/;
// let order = {
// Data: _this.order,
// Op: "stdin"
// };
let order = _this.order
if (!reg.test(_this.order)) {
term.writeln("请输入有效指令~");
} else {
// 发送数据
_this.inputList.push(_this.order);
last = _this.inputList.length - 1;
_this.onSend(order);
// 清空输入内容变量
}
} else if (event.domEvent.keyCode === 8) {
// 当输入退
// 当前行字符长度如果等于后端返回字符就不进行删除
if(_this.order.length > 0) {
_this.order = _this.order.substr(0, _this.order.length - 1);
term.write('\b \b')
}
// if (term._core.buffer.x > _this.showOrder.length) {
// term.write("\b \b"); // 输出退格
// }
// // 将输入内容变量删除
// if (_this.trim(_this.order) == _this.trim(_this.showOrder)) {
// return false;
// } else {
// _this.order = _this.order.substr(0, _this.order.length - 1);
// term.write('\b \b')
// }
} else if (event.domEvent.keyCode === 127) {
if (term._core.buffer.x > (_this.showOrder.length + 2)) {
term.write('\b \b')
_this.order = _this.order.substr(0, _this.order.length - 1)
}
} else if (event.domEvent.keyCode === 38 || event.domEvent.keyCode === 40) {
let len = _this.inputList.length;
let code = event.domEvent.keyCode;
if (code === 38 && last <= len && last >= 0) {
// 直接取出字符串数组最后一个元素
let inputVal = _this.inputList[last];
term.write(inputVal);
if (last > 0) {
last--;
}
}
if (code === 40 && last < len) {
// last现在为当前元素
if (last == len - 1) {
return;
}
if (last < len - 1) {
last++;
}
let inputVal = _this.inputList[last];
term.write(inputVal);
}
} else if (event.domEvent.keyCode === 9) {
// 如果按tab键前输入了之前后端返回字符串的第一个字符就显示此命令
if (_this.order !== "" && show.indexOf(_this.order) == 0) {
term.write(_this.showOrder);
}
} else if (event.key == '\x16') {
navigator.clipboard.readText().then(clipText => {
term.write(clipText);
})
//ctrol+ c copy
} else if (event.key == '\x03' && term.hasSelection()) {
navigator.clipboard.writeText(term.getSelection())
} else if (printable) {
let key = event.key
// // 当为可打印内容时
// if (/[a-zA-Z]/.test(key)) {
// key = key.toLowerCase();
// }
// 存入输入内容变量
_this.order = _this.order + key;
// 将变量写入终端内
term.write(key);
}
});
_this.term = term;
}
runFakeTerminal(_this);
},
methods: {
/**
* **wsShell 创建页面级别的websocket,加载页面数据
* ws 接口:/xxx/xxx/xxx
* 参数:无
* ws参数:
* @deployId 任务id
* @tagString 当前节点
* 返回:无
* **/
wsShell(url) {
const _this = this;
let tag = this.urlParam.Tag;
let name= this.urlParam.name;
let pod= this.urlParam.pod;
// let query = `?tag=${tag}&name=${name}&pod=${pod}`;
// let url = `xxxx/xxxx${query}`;// websocket连接接口
// let url = 'ws://172.16.0.120:30598/ws/target-19-austria-80-110-35-0-24/target-19-relay-127-54b5df76f4-8958g/relay-127'
this.shellWs = this.websoketLib.WS({
url,
isInit: true,
openFn(e) {
console.log('连接websocket成功===', e)
// _this.term.resize({ rows: _this.rows, cols: 100 }); //终端窗口重新设置大小 并触发term.on("resize")
},
messageFn(e) {
console.log("message", e);
if (e) {
// let data = JSON.parse(e.data);
// if (data.Data == "\n" || data.Data == "\r\nexit\r\n") {
// _this.$message("连接已关闭");
// }
// 打印后端返回数据
// _this.term.write(data.Data);
let backData = ''
// 创建一个包含文本内容的Blob对象
var blob = e.data;
// 创建一个新的FileReader对象
var reader = new FileReader();
// 读取Blob并将其转换为字符串
reader.onloadend = function () {
backData = reader.result; // 获得转换后的字符串
console.log(backData, '服务端websockt返回结果===='); // 输出结果到控制台
let index = _this.order?.length;
backData = backData.substring(index, backData.length);
// 如果返回字符包含这些字符显示close提示
if (backData == "\n" || backData == "\r\nexit\r\n") {
alert("closed");
}
if (backData.trim() === 'Connected!') {
_this.term.write(backData)
_this.term.write(`root@${_this.commandPrefix}:/#`);
} else {
_this.term.write(backData)
}
_this.showOrder = backData;
_this.order = "";
};
// 开始读取Blob
reader.readAsText(blob);
}
},
errorFn(e) {
//出现错误关闭当前ws,并且提示
console.log("error", e);
_this.$message.error({
message: "ws 请求失败,请刷新重试~",
duration: 5000
});
}
});
},
onSend(data) {
// data = this.websoketLib.isObject(data) ? JSON.stringify(data) : data;
// data = this.websoketLib.isArray(data) ? data.toString() : data;
// data = data.replace(/\\\\/, "\\");
this.shellWs.onSend(data);
},
//删除左右两端的空格
trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "");
},
// 获取websocket地址
getWebsocketUrl() {
const reqParams = { node_id: this.nodeId }
this.$axios.get(this.$http.api.getWebsocketUrl, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const wsUrl = 'ws://' + window.g.baseURL + res.result
this.wsShell(wsUrl)
this.commandPrefix = res.result.split('/')[3]
}
}).catch(err => {
console.log(err)
})
},
// 全屏时重新设置控制台行高
onResize() {
this.rows = document.querySelector(".indexContainer").offsetHeight / 16 - 6;
this.cols = document.querySelector(".indexContainer").offsetWidth / 14;
this.term.resize(parseInt(this.rows), parseInt(this.cols))
// canvas背景全屏
var fitAddon = new FitAddon();
this.term.loadAddon(fitAddon);
try {
fitAddon.fit();
// 窗口大小改变时触发xterm的resize方法向后端发送行列数格式由后端决定
// 这里不使用size默认参数因为改变窗口大小只会改变size中的列数而不能改变行数所以这里不使用size.clos,而直接使用获取我们根据窗口大小计算出来的行列数
term.onResize(() => {
this.onSend({ Op: "resize", Cols: term.cols, Rows: term.rows });
});
} catch (e) {
console.log("e", e.message);
}
},
}
};
</script>
<style lang="less" scoped>
.indexContainer {
height: calc(100% - 0px);
}
</style>

View File

@@ -1,306 +0,0 @@
<template>
<!-- <div class="content"> -->
<!-- <div id="xterm" class="xterm"/> -->
<div id="log" style="margin:10px auto;">
<div class="console" id="terminal"></div>
</div>
<!-- </div> -->
</template>
<script>
import "xterm/css/xterm.css";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { AttachAddon } from "xterm-addon-attach";
export default {
name: "Xterm",
props: {
// socketURI: {
// type: String,
// default: ''
// },
},
data() {
return {
term: null, //terminal 黑窗口容器
socket: null,
rows: 32,
cols: 20,
SetOut: false,
isKey: false,
prefix: "[root@serverip ~]# "//前缀
// inputText: "",//输入内容每次回车后进行ws通信然后清空此数据
};
},
watch: {},
mounted() {
this.initSocket()
},
beforeDestroy() {
this.socket.close()
// this.term.dispose()
},
methods: {
//初始化黑窗口
async initTerm() {
const term = new Terminal({
rendererType: "canvas", //渲染类型
rows: this.rows, //行数
// cols: this.cols,// 设置之后会输入多行之后覆盖现象
convertEol: true, //启用时,光标将设置为下一行的开头
// scrollback: 10,//终端中的回滚量
fontSize: 14, //字体大小
disableStdin: false, //是否应禁用输入。
cursorStyle: "block", //光标样式
// cursorBlink: true, //光标闪烁
scrollback: 30,
tabStopWidth: 4,
theme: {
foreground: "yellow", //字体
background: "#060101", //背景色
cursor: "help" //设置光标
}
});
const attachAddon = new AttachAddon(this.socket);
const fitAddon = new FitAddon();
term.loadAddon(attachAddon);
term.loadAddon(fitAddon);
//开启Xterm终端
term.open(document.getElementById("terminal"));
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ');
term.focus();
this.term = term;
// if (term._initialized) {
// return
// }
// term._initialized = true
// term.prompt = () => {
// term.write('\r\n$ ')
// }
// term.writeln('Welcome to xterm.js')
// term.writeln('This is a local terminal emulation, Type some keys and commands to play around')
// term.writeln('')
// term.prompt()
// // xterm.4.x 输入
// term.onKey(e => {
// const ev = e.domEvent
// const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
// if (ev.keyCode === 13) {
// term.prompt()
// } else if (ev.keyCode === 8) {
// // Do not delete the prompt
// if (term._core.buffer.x > 2) {
// term.write('\b \b')
// }
// } else if (printable) {
// term.write(e.key);
// this.socket.send(e.key);
// }
// })
// this.term = term
},
// //事件
// termKeyCode() {
// const TERMINAL_INPUT_KEY = {
// BACK: 8, // 退格删除键
// ENTER: 13, // 回车键
// UP: 38, // 方向盘上键
// DOWN: 40, // 方向盘键
// LEFT: 37, // 方向盘左键
// RIGHT: 39, // 方向盘右键
// };
// const { eqpCode, server } = this.selectObj;
// let inputText = "";
// let currentIndex = 0;
// let inputTextList = [];
// this.term.onKey((e) => {
// const { key, domEvent } = e;
// const { keyCode, altKey, altGraphKey, ctrlKey, metaKey } = domEvent;
// const printAble = !(altKey || altGraphKey || ctrlKey || metaKey); // 禁止相关按键
// const totalOffsetLength = inputText.length + this.prefix.length; // 总偏移量
// const currentOffsetLength = this.term._core.buffer.x; // 当前x偏移量
// switch (keyCode) {
// //删除
// case TERMINAL_INPUT_KEY.BACK:
// if (currentOffsetLength > this.prefix.length) {
// const cursorOffSetLength = this.getCursorOffsetLength(totalOffsetLength - currentOffsetLength, "\x1b[D"); // 保留原来光标位置
// this.term._core.buffer.x = currentOffsetLength - 1;
// this.term.write("\x1b[?K" + inputText.slice(currentOffsetLength - this.prefix.length));
// this.term.write(cursorOffSetLength);
// inputText = `${inputText.slice(0, currentOffsetLength - this.prefix.length - 1)}${inputText.slice(
// currentOffsetLength - this.prefix.length
// )}`;
// }
// break;
// //回车
// case TERMINAL_INPUT_KEY.ENTER: {
// this.term.write("\r\n");
// console.log("inputText", inputText);
// //ws 通信参数
// let wsParams = { EqpCode: eqpCode, Action: "terminal", Data: inputText };
// this.$emit("websocketSend", wsParams, server);
// if (!inputText.trim()) {
// this.term.prompt();
// return;
// }
// if (inputTextList.indexOf(inputText) === -1) {
// inputTextList.push(inputText);
// currentIndex = inputTextList.length;
// }
// this.term.prompt();
// inputText = "";
// break;
// }
// case TERMINAL_INPUT_KEY.UP: {
// if (!inputTextList[currentIndex - 1]) break;
// const offsetLength = this.getCursorOffsetLength(inputText.length, "\x1b[D");
// inputText = inputTextList[currentIndex - 1];
// this.term.write(offsetLength + "\x1b[?K");
// this.term.write(inputTextList[currentIndex - 1]);
// this.term._core.buffer.x = totalOffsetLength;
// currentIndex--;
// break;
// }
// case TERMINAL_INPUT_KEY.LEFT:
// if (currentOffsetLength > this.prefix.length) {
// this.term.write(key); // '\x1b[D'
// }
// break;
// case TERMINAL_INPUT_KEY.RIGHT:
// if (currentOffsetLength < totalOffsetLength) {
// this.term.write(key); // '\x1b[C'
// }
// break;
// default: {
// // 在当前的坐标写上 key 和坐标后面的字符
// // 移动停留在当前位置的光标
// if (!printAble) break;
// if (totalOffsetLength >= this.term.cols) break;
// if (currentOffsetLength >= totalOffsetLength) {
// this.term.write(key);
// inputText += key;
// break;
// }
// let cursorOffSetLength = this.getCursorOffsetLength(totalOffsetLength - currentOffsetLength, "\x1b[D");
// this.term.write("\x1b[?K" + `${key}${inputText.slice(currentOffsetLength - this.prefix.length)}`);
// this.term.write(cursorOffSetLength);
// inputText = inputText.slice(0, currentOffsetLength) + key + inputText.slice(totalOffsetLength - currentOffsetLength);
// break;
// }
// }
// });
// },
// //限制和后端交互,只有输入回车键才显示结果
// termPromt() {
// this.term.prompt = () => {
// this.term.write(this.prefix);
// };
// },
//获取光标当前位置
getCursorOffsetLength(offsetLength, subString) {
let cursorOffsetLength = "";
for (let offset = 0; offset < offsetLength; offset++) {
cursorOffsetLength += subString;
}
return cursorOffsetLength;
},
//写入黑窗口
wirteTerm(data) {
console.log("写入黑窗口", data);
this.term.writeln(data);
this.term.prompt();
},
// //加载基础数据
// pageLoad(data) {
// this.selectObj = data;
// this.drawerFlag = true;
// this.$nextTick(() => {
// this.initTerm();
// });
// },
cancelClick() {
this.drawerFlag = false;
//关闭弹框
this.term.dispose(document.getElementById("xterm"));
},
initSocket() {
const wsurl = 'ws://172.16.0.120:30598/ws/target-19-austria-80-110-35-0-24/target-19-relay-127-54b5df76f4-8958g/relay-127'
this.socket = new WebSocket(wsurl);
this.socketOnClose();
this.socketOnOpen();
this.socketOnError();
},
socketOnOpen() {
this.socket.onopen = () => {
console.log('socket 连接成功')
// 链接成功后
this.initTerm()
}
// this.socket.onmessage = function(evt) {
// // let str = new TextDecoder().decode(evt.data);
// // console.log('onmessage=====', evt)
// this.term.write(evt.data);
// };
// //返回
// this.socket.onmessage = function(evt) {
// // let str = new TextDecoder().decode(evt.data);
// console.log('onmessage=====', evt)
// this.term.write(evt.data);
// };
},
socketOnClose() {
this.socket.onclose = (e) => {
console.log('socket 关闭:' + '错误码==' + e.code + ';错误原因==' + e.reason + 'wasClean==' + e.wasClean)
}
},
socketOnError() {
this.socket.onerror = () => {
console.log('socket 连接失败')
}
}
},
};
</script>
<style lang='less' scoped="scoped">
// .content {
// ::v-deep .el-textarea__inner {
// background-color: #1A2648;
// }
// }
.xterm-screen{
min-height: calc(100vh);
}
</style>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -1,349 +0,0 @@
<template>
<div
style="height: 100%;
background: #002833;"
>
<div class="indexContainer" id="terminal" ref="terminal"></div>
</div>
</template>
<script>
// 引入xterm请注意这里和3.x版本的引入路径不一样
import { Terminal } from "xterm";
import "xterm/css/xterm.css";
import "xterm/lib/xterm.js";
import { FitAddon } from "xterm-addon-fit";
export default {
name: "Shell",
data() {
return {
nodeId: '',
shellWs: "",
term: "", // 保存terminal实例
rows: 40,
cols: 100,
urlParam: {
Tag: 'tag',
name: 'name',
pod: 'pod'
}
};
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
},
immediate: true
}
},
created() {
this.getWebsocketUrl()
},
mounted() {
let _this = this;
localStorage.setItem('commands', '')
// 获取容器宽高/字号大小,定义行数和列数
// this.rows = document.querySelector(".indexContainer").offsetHeight / 16 - 6;
// this.cols = document.querySelector(".indexContainer").offsetWidth / 14;
let term = new Terminal({
rendererType: "canvas", //渲染类型
// rows: parseInt(_this.rows), //行数
// cols: parseInt(_this.cols), // 不指定行数,自动回车后光标从下一行开始
convertEol: true, //启用时,光标将设置为下一行的开头
// scrollback: 50, //终端中的回滚量
disableStdin: false, //是否应禁用输入。
cursorStyle: "underline", //光标样式
cursorBlink: true, //光标闪烁
theme: {
foreground: "#7e9192", //字体
background: "#002833", //背景色
cursor: "help", //设置光标
lineHeight: 16
}
});
// 创建terminal实例
term.open(this.$refs["terminal"]);
// 换行并输入起始符“$”
term.prompt = () => {
term.write("\r\n$ ");
};
term.prompt();
// // canvas背景全屏
var fitAddon = new FitAddon();
term.loadAddon(fitAddon);
fitAddon.fit();
window.addEventListener("resize", resizeScreen);
// 内容全屏显示
function resizeScreen() {
// 不传size
try {
fitAddon.fit();
// 窗口大小改变时触发xterm的resize方法向后端发送行列数格式由后端决定
// 这里不使用size默认参数因为改变窗口大小只会改变size中的列数而不能改变行数所以这里不使用size.clos,而直接使用获取我们根据窗口大小计算出来的行列数
term.onResize(() => {
_this.onSend({ Op: "resize", Cols: term.cols, Rows: term.rows });
});
} catch (e) {
console.log("e", e.message);
}
}
// function runFakeTerminal(_this) {
// if (term._initialized) {
// return;
// }
// // 初始化
// term._initialized = true;
// term.writeln("Welcome to use Superman. ");
// term.writeln(
// `This is Web Terminal of pod\x1B[1;3;31m ${
// _this.urlParam.podName
// }\x1B[0m in namespace\x1B[1;3;31m ${_this.urlParam.namespace}\x1B[0m`
// );
// term.prompt();
// // / **
// // *添加事件监听器,用于按下键时的事件。事件值包含
// // *将在data事件以及DOM事件中发送的字符串
// // *触发了它。
// // * @返回一个IDisposable停止监听。
// // * /
// // / ** 更新xterm 4.x新增
// // *为数据事件触发时添加事件侦听器。发生这种情况
// // *用户输入或粘贴到终端时的示例。事件值
// // *是`string`结果的结果,在典型的设置中,应该通过
// // *到支持pty。
// // * @返回一个IDisposable停止监听。
// // * /
// // 支持输入与粘贴方法
// term.onData(function(key) {
// let order = {
// Data: key,
// Op: "stdin"
// };
// _this.onSend(key);
// // 为解决窗体resize方法才会向后端发送列数和行数所以页面加载时也要触发此方法
// _this.onSend({
// Op: "resize",
// Cols: parseInt(term.cols),
// Rows: parseInt(term.rows)
// });
// });
// _this.term = term;
// }
// runFakeTerminal(_this);
function runFakeTerminal(_this) {
if (term._initialized) {
return;
}
term._initialized = true;
term.prompt = () => {
term.write("\r\n ");
};
term.writeln("Welcome to xterm.js");
term.writeln(
"This is a local terminal emulation, without a real terminal in the back-end."
);
term.writeln("Type some keys and commands to play around.");
term.writeln("");
term.prompt();
// 监控键盘输入事件
// / **
// *添加事件监听器,用于按下键时的事件。事件值包含
// *将在data事件以及DOM事件中发送的字符串
// *触发了它。
// * @返回一个IDisposable停止监听。
// * /
// 添加事件监听器,支持输入方法
term.onData( function (key) {
if (key.charCodeAt(0) == 13) { // 回车
if(_this.command === 'clear') {
term.clear()
}
if (_this.command.trim().length === 0) {
term.prompt()
} else {
// 保存命令
let commands = localStorage.getItem('commands') ? JSON.parse(localStorage.getItem('commands')) : []
commands.push(_this.command)
localStorage.setItem('commands', JSON.stringify(commands))
localStorage.setItem('index', commands.length)
// _this.sendData(0)
_this.onSend(localStorage.getItem('commands'))
}
_this.command = ''
} else if (key === '\u001b[A') { // 向上方向
let commands = localStorage.getItem('commands') ? JSON.parse(localStorage.getItem('commands')) : []
// console.log(commands)
let index = localStorage.getItem('index') ? localStorage.getItem('index') : commands.length
index = parseInt(index)
if (commands.length && index < commands.length + 1 && index > 0) {
// 删除现有命令
for (let i = 0; i < _this.command.length; i++) {
if (term._core.buffer.x > (_this.sshPrompt.length + 2)) {
term.write('\b \b')
}
}
_this.command = commands[index - 1]
term.write(_this.command)
localStorage.setItem('index', index - 1)
}
} else if (key === '\u001b[B') { // 向下方向
let commands = localStorage.getItem('commands') ? JSON.parse(localStorage.getItem('commands')) : []
let index = localStorage.getItem('index') ? localStorage.getItem('index') : commands.length
index = parseInt(index)
if (commands.length && index < commands.length - 1 && index > -1) {
// 删除现有命令
for (let i = 0; i < _this.command.length; i++) {
if (term._core.buffer.x > (_this.sshPrompt.length + 2)) {
term.write('\b \b')
}
}
_this.command = commands[index + 1]
term.write(_this.command)
localStorage.setItem('index', index + 1)
}
} else if (key.charCodeAt(0) === 9) { // tab键
let params = {
consoleUUID: _this.activeMsf,
cmd: _this.command
}
// tab补全
_this.$apis.readTabsComplete(params).then((res) => {
if (res.code === 200) {
if (res.data.length) {
for (let i = 0; i < _this.command.length; i++) {
term.write('\b \b')
}
let data = res.data.join('\r\n')
_this.command = res.data[res.data.length - 1]
if (res.data.length > 1) {
term.write('\r\n')
term.write(data)
term.prompt()
term.write(res.data[res.data.length - 1])
} else {
term.write(_this.command)
}
}
} else {
_this.$message.error(res.message())
}
})
} else if (key.charCodeAt(0) === 127) {
if (term._core.buffer.x > (_this.sshPrompt.length + 2)) {
term.write('\b \b')
_this.command = _this.command.substr(0, _this.command.length - 1)
}
} else{
_this.command += key
term.write(key)
}
})
_this.term = term;
// 粘贴事件
term.onData(function(data) {
_this.order = data;
term.write(data);
});
}
runFakeTerminal(_this);
},
methods: {
/**
* **wsShell 创建页面级别的websocket,加载页面数据
* ws 接口:/xxx/xxx/xxx
* 参数:无
* ws参数:
* @deployId 任务id
* @tagString 当前节点
* 返回:无
* **/
wsShell(url) {
const _this = this;
let tag = this.urlParam.Tag;
let name= this.urlParam.name;
let pod= this.urlParam.pod;
// let query = `?tag=${tag}&name=${name}&pod=${pod}`;
// let url = `xxxx/xxxx${query}`;// websocket连接接口
// let url = 'ws://172.16.0.120:30598/ws/target-19-austria-80-110-35-0-24/target-19-relay-127-54b5df76f4-8958g/relay-127'
this.shellWs = this.websoketLib.WS({
url,
isInit: true,
openFn(e) {
console.log('连接websocket成功===', e)
// _this.term.resize({ rows: _this.rows, cols: 100 }); //终端窗口重新设置大小 并触发term.on("resize")
},
messageFn(e) {
console.log("message", e);
if (e) {
_this.term.write(e.data);
// let data = JSON.parse(e.data);
// if (data.Data == "\n" || data.Data == "\r\nexit\r\n") {
// _this.$message("连接已关闭");
// }
// 打印后端返回数据
// _this.term.write(data.Data);
}
},
errorFn(e) {
//出现错误关闭当前ws,并且提示
console.log("error", e);
_this.$message.error({
message: "ws 请求失败,请刷新重试~",
duration: 5000
});
}
});
},
onSend(data) {
// data = this.websoketLib.isObject(data) ? JSON.stringify(data) : data;
// data = this.websoketLib.isArray(data) ? data.toString() : data;
// data = data.replace(/\\\\/, "\\");
this.shellWs.onSend(data);
},
//删除左右两端的空格
trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "");
},
// 获取websocket地址
getWebsocketUrl() {
const reqParams = { node_id: this.nodeId }
this.$axios.get(this.$http.api.getWebsocketUrl, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.wsShell(res.result)
}
}).catch(err => {
console.log(err)
})
}
}
};
</script>

View File

@@ -1,95 +0,0 @@
<template>
<div class="role-dialog" v-if="visible">
<i class="el-icon-close" style="float: right; padding-right: 7%;padding-top: 2.8%" @click="close"></i>
<div class="fbs">
<span style="margin-right: 3%">文件名</span>
<el-input
size="mini"
:value="value"
placeholder="请输入内容"
style="width: 50%"
v-bind="$attrs"
v-on="$listeners">
</el-input>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="submit">确认</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RoleForm',
props: {
value: {
typeof: String,
require: true,
default: ''
}
},
data() {
return {
visible: false
}
},
watch: {
value: {
handler(val) {
this.$emit('input', val)
}
}
},
methods: {
close() {
// document.querySelector('.mask').style.display = 'none'
this.visible = false
},
submit() {
// document.querySelector('.mask').style.display = 'none'
this.visible = false
this.$emit('makeDirSubmit')
}
}
}
</script>
<style lang="less" scoped>
.role-dialog{
z-index: 998;
width: 300px;
height: 120px;
position: absolute; /* 绝对定位 */
top: 100px; /* 向下偏移50% */
left: 150px; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../../img/jbpzxybqr.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.fbs{
width: 100%;
float: left;
margin-top: 3%;
text-align: center;
}
.anDiv{
width: 100%;
float: left;
margin-top: 5%;
text-align: center;
.glBut{
width: 50px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
</style>

View File

@@ -1,538 +0,0 @@
<template>
<div class="content">
<el-tree
ref="tree"
class="tree-section"
lazy
node-key="id"
:data="treeData"
:props="defaultProps"
:load="loadNode"
@node-click="handleNodeClick"
@node-contextmenu="openTreeMenu"
v-dragresize="dragConfig"
>
</el-tree>
<div class="tree-content">
<div style="text-align:left; width: 100%; padding: 10px;border: 1px solid #BAD0F11A;">
<span>
{{ dirTitle }}
</span>
</div>
<div style="clear:both;text-align:left; padding: 10px;border-bottom: 1px solid #BAD0F11A;">
<img src="../../../../../img/icon/file-icon.svg" alt="" style="width: 18px; height: 18px;">
{{ currentFileName }}
</div>
<ul style="clear:both;" class="tree-content-ul">
<li v-for="fileItem in fileList" :key="fileItem.name" @contextmenu.prevent="openMenu(fileItem, $event)">
<i class="el-icon-document file-info" style="font-size: 20px"></i>
<span class="file-info">{{ fileItem.name }}</span>
<span class="file-info">{{ fileItem.size }}</span>
<span class="file-info">{{ fileItem.time }}</span>
</li>
</ul>
<div class="empty-box" @contextmenu.prevent="openUploadMenu($event)"></div>
</div>
<ul
v-show="visible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li>
<li @click="download">下载</li>
<li @click="del">删除</li>
</ul>
<ul
v-show="uploadVisible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li>
</ul>
<div v-show="showTreeMenu" class="treeMenu">
<div @click="makeDir">新建文件夹</div>
<div @click="delDir">删除文件夹</div>
</div>
<DirNameForm
ref="dirNameForm"
v-model="dirName"
@makeDirSubmit="makeDirSubmit">
</DirNameForm>
</div>
</template>
<script>
import DirNameForm from './DirNameForm.vue'
export default {
components: { DirNameForm },
props: ['isFullscreen'],
data() {
return{
visible: false,
uploadVisible: false,
top: 0,
left: 0,
nodeId: '',
dirTitle: '>',
currentFileName: '',
fileList: [],
selectFile: {},
selectNode: {path: '/'},
rightClickNode: {},
defaultProps: {
children: 'children',
label: 'name',
},
showTreeMenu: false, // 文件夹数据
contextNode: {}, // 文件夹数据
dirName: '', // 新建文件名
treeData: []
}
},
computed: {
dragConfig() {
return [
{
dragBorder: "right",
setCssProperty: "width",
},
];
},
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
// this.getCurrentDirFile('/')
},
immediate: true
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
},
uploadVisible(value) {
if (value) {
document.body.addEventListener('click', this.closeUploadMenu)
} else {
document.body.removeEventListener('click', this.closeUploadMenu)
}
},
},
methods: {
upload(e) {
const params = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
file: e.target.files[0]
}
const submitForm = new FormData()
for(const key in params) {
submitForm.append(key, params[key])
}
this.$axios.postFormData(this.$http.api.uploadFile, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '上传文件成功',
type: 'success',
duration: 2500
})
// this.$refs.tree.remove(item.id);
// this.setCurrentHighlight(null);
// console.log('选中了节点id===', this.selectNode.$treeNodeId)
// 更新当前路径下的文件信息
this.handleNodeClick(this.selectNode)
this.setCurrentHighlight(this.selectNode.$treeNodeId);
// this.init()
}
}).catch(err => {
console.log(err)
})
},
download() {
// this.downLoading = true
const reqParams = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
filename: this.selectFile.name
}
this.$axios.getFile(this.$http.api.downloadFile, reqParams).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载文件成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载文件失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
// this.downLoading = false
})
},
del() {
const params = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
filename: this.selectFile.name
}
this.$axios.delete(this.$http.api.delFile, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 更新当前路径下的文件信息
this.handleNodeClick(this.selectNode)
// console.log('选中了节点id===', this.selectNode.$treeNodeId)
this.setCurrentHighlight(this.selectNode.$treeNodeId);
// this.init()
}
}).catch(err => {
console.log(err)
})
},
// 设置当前选中高亮
setCurrentHighlight (id) {
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id)
})
},
openMenu(fileItem, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 180 // fix 位置bug
} else {
this.top = e.clientY - 600 // fix 位置bug
}
this.uploadVisible = false
this.visible = true
this.selectFile = fileItem
},
closeMenu() {
this.visible = false
},
openUploadMenu(e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 120 // fix 位置bug
} else {
this.top = e.clientY - 600 // fix 位置bug
}
this.visible = false
this.uploadVisible = true
},
closeUploadMenu() {
this.uploadVisible = false
},
// 获取当前路径下文件
getCurrentDirFile(currentDir) {
const reqParams = { node_id: this.nodeId, current_dir: currentDir }
return this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const dirs = res?.result?.object?.dir || []
this.fileList = res?.result?.object?.file || []
// console.log('请求回来files===', files)
const nodeData = dirs.map(dir => {
return {
name: dir.name,
path: currentDir === '/' ? currentDir + dir.name : `${currentDir}/${dir.name}`,
fileList: []
}
})
// console.log('请求回来nodeData====', nodeData)
return nodeData
}
}).catch(err => {
console.log(err)
})
},
// 点击加载子节点
async loadNode(node, reslove) {
// console.log('执行了loadNode', node)
if (node.level == 0) {
let nodeData = await this.getCurrentDirFile('/')
// console.log('初始化数据===', nodeData)
// this.selectNode = nodeData.fileList
return reslove(nodeData)
} else {
let nodeData = await this.getCurrentDirFile(node.data.path)
// 判断是否为最底层节点 根据自己接口返回的数据判断即可
nodeData.forEach(item => {
item.hasChildren == false ? item.lastNode = ture : null
});
// this.selectNode = nodeData.fileList
return reslove(nodeData)
}
},
handleNodeClick(node) {
// this.fileList = []
// console.log('选中了节点===', node)
// console.log('选中了节点id===', node.$treeNodeId)
this.selectNode = node
this.dirTitle = node.path.split('/').join('>')
this.currentFileName = node.name
// this.fileList = node.fileList
const reqParams = { node_id: this.nodeId, current_dir: node.path }
this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.fileList = res?.result?.object?.file || []
}
}).catch(err => {
console.log(err)
})
},
openTreeMenu(event, data, node, target) {
// console.log(event, data, node, target)
this.showTreeMenu = true // 显示菜单
this.contextNode = data // 存储数据
// console.log(this.contextNode, 'contextNode')
// console.log(node, 'rightClickNode=======')
// console.log(target, 'target')
this.rightClickNode = node
document.querySelector('.treeMenu').setAttribute('style',`top:${event.clientY + 30}px;left:${event.clientX + 30}px;`)
document.addEventListener('click', this.closeTreeMenu)
document.addEventListener('contextmenu', this.closeTreeMenu)
},
closeTreeMenu() {
this.showTreeMenu = false // 关闭菜单
document.removeEventListener('click', this.closeTreeMenu)
document.removeEventListener('contextmenu', this.closeTreeMenu)
},
// 当新建文件夹时,先填写名字
openDirNameForm() {
this.$refs.dirNameForm.visible = true
},
// 新建文件夹
makeDir() {
this.openDirNameForm()
},
// 新建文件夹接口提交
makeDirSubmit() {
const reqParams = { node_id: this.nodeId, current_dir: this.contextNode.path, new_dir_name: this.dirName }
this.$axios.get(this.$http.api.makeDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '新建文件夹成功',
type: 'success',
duration: 2500
})
// 页面增加子节点
let id = Math.ceil(Math.random() * 1000000)
var addNode = {
path: this.contextNode.path + '/' + this.dirName,
name: this.dirName,
fileList: []
// id: id,
// name: this.dirName,
// data: {
// path: this.contextNode.path,
// name: this.dirName,
// fileList: []
// }
}
// console.log(addNode, 'addNode======')
this.$refs.tree.append(addNode, this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
},
// 删除文件夹
delDir() {
const index = this.contextNode.path.lastIndexOf('/')
const path = this.contextNode.path.substring(0, index);
const params = {
node_id : this.nodeId,
current_dir: path,
dir_name: this.contextNode.name
}
this.$axios.delete(this.$http.api.delDir, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 页面删除子节点
this.$refs.tree.remove(this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang='less' scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
color: #ffffff;
.tree-section {
width: 25%;
height: 100%;
// background-color: #1A2648;
background: transparent;
border: 1px solid #BAD0F11A;
color: #06F7FF;
overflow-y: auto;
::v-deep .el-tree-node__content:hover {
background-color: rgba(14, 61, 138, 0.3) !important;
}
::v-deep .el-tree-node.is-current > .el-tree-node__content {
background-color: rgba(14, 61, 138, 0.3) !important;
}
}
.tree-content {
width: 75%;
height: 100%;
display: flex;
flex-direction: column;
.tree-content-ul {
width: 100%;
background-color: rgba(25, 33, 61, 0.4);
overflow-y: auto;
li {
display: flex;
flex-direction: row;
justify-content: space-around;
border-bottom: 1px solid #BAD0F11A;
padding: 5px;
.file-info {
flex: 1 1 100px;
}
}
}
.empty-box {
flex: 1;
}
}
}
.contextmenu {
margin: 0;
border-radius: 6px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
background: #0E3D8A;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
font-size: 12px;
font-weight: 400;
color: #06F7FF;
// box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
li {
margin: 0;
padding: 7px 30px;
cursor: pointer;
&:hover {
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
}
}
.file-input {
display: none;
}
}
.treeMenu {
position: fixed;
z-index: 99999;
top: 50%;
left: 50%;
color: #06F7FF;
background-color: #0E3D8A;
overflow: hidden;
border-radius: 5px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
div{
padding: 7px 30px;
box-sizing: border-box;
//width: 50px;
text-align: center;
}
div:hover{
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
cursor: pointer;
}
}
</style>

View File

@@ -1,107 +0,0 @@
<template>
<div class="tabs">
<i class="el-icon-back back-btn" @click="backNodeList"></i>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="详细信息" name="second">
<DetailInfo></DetailInfo>
</el-tab-pane>
<el-tab-pane label="资源监控" name="third">
<ResourceMonitor v-if="activeName==='third'"></ResourceMonitor>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DetailInfo from './module/DetailInfo'
import ResourceMonitor from './module/ResourceMonitor'
export default {
components: { DetailInfo, ResourceMonitor },
data() {
return {
activeName: 'second'
};
},
methods: {
handleClick(tab, event) {
},
backNodeList() {
this.$router.push({ name: 'rangeNodeManage' })
}
}
}
</script>
<style lang='less' scoped="scoped">
.tabs {
width: 100%;
height: 100%;
text-align: center;
position: relative;
padding: 8px 0 0;
display: flex;
.detail-title {
position:absolute;
margin: 10px;
}
.back-btn {
position: absolute;
cursor: pointer;
font-size: 30px;
color: #02DDEA;
margin: 5px;
z-index: 10;
}
}
::v-deep .el-tabs{
color: #000;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
position: relative;
.el-tabs__header {
margin-left: 100px;
}
.el-tabs__content{
height: calc(100% - 48px);
// height: 100%;
.el-tab-pane{
height: 100%;
}
}
}
/* 去除灰色横条 */
::v-deep .el-tabs__nav-wrap::after {
position: static !important;
}
/* 设置滑块颜色 */
::v-deep .el-tabs__active-bar{
background-color: #0E3D8A !important;
}
/* 设置滑块停止位置 */
::v-deep .el-tabs__active-bar.is-top{
height: 37px;
width: 104px ! important;
border-radius: 17px;
top: 0px !important;
left: -18px !important;
position: absolute !important;
z-index: 1;
}
/* 设置当前选中样式 */
::v-deep .el-tabs__item.is-active{
color:#02DDEA !important;
z-index: 2;
}
/* 设置未被选中样式 */
::v-deep .el-tabs__item{
padding: 0 20px !important;
width: 104px;
box-sizing: border-box;
display: inline-block;
position: relative !important;
color:#02DDEA !important;
z-index: 2;
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
<div class="detail-info" v-loading="loading">
<el-descriptions :column="2">
<el-descriptions-item label="Tor版本">{{nodeDetail.owner_setting && nodeDetail.owner_setting.tor_version}}</el-descriptions-item>
<el-descriptions-item label="状态">{{nodeDetail.status}}</el-descriptions-item>
<el-descriptions-item label="角色">{{nodeDetail.owner_setting && nodeDetail.owner_setting.role}}</el-descriptions-item>
<el-descriptions-item label="创建开始时间">{{formatTime(nodeDetail.create_time)}}</el-descriptions-item>
<el-descriptions-item label="宽带">{{nodeDetail.owner_setting && nodeDetail.owner_setting.bandwidth + '&nbsp;M'}}</el-descriptions-item>
<el-descriptions-item label="创建完成时间">{{formatTime(nodeDetail.complete_time)}}</el-descriptions-item>
<el-descriptions-item label="内存">{{nodeDetail.owner_setting && nodeDetail.owner_setting.memory + '&nbsp;Mi'}}</el-descriptions-item>
<el-descriptions-item label="ip">{{nodeDetail.ip}}</el-descriptions-item>
<el-descriptions-item label="sockport">{{nodeDetail.owner_setting && nodeDetail.owner_setting.socks_port}}</el-descriptions-item>
<el-descriptions-item label="pod名">{{nodeDetail.pod_name}}</el-descriptions-item>
<el-descriptions-item label="网络">{{nodeDetail.owner_setting && nodeDetail.owner_setting.use_network.cidr}}</el-descriptions-item>
<el-descriptions-item label="节点昵称">{{nodeDetail.nick_name}}</el-descriptions-item>
<el-descriptions-item v-if="nodeDetail.owner_setting && nodeDetail.owner_setting.role==='onion'" label="onion">{{nodeDetail.onion}}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script>
import { formatTime } from '../../../../../utils'
export default {
name: 'DetailInfo',
data() {
return {
loading: false,
nodeId: '',
nodeDetail: {}
}
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
this.init()
},
immediate: true
}
},
methods: {
formatTime,
init() {
// this.nodeDetail = getTargetsResponse?.result
this.loading = true
const reqParams = { node_id: this.nodeId }
this.$axios.get(this.$http.api.getNodeDetail, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.nodeDetail = res?.result
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang='less' scoped="scoped">
.detail-info {
width: 100%;
height: 100%;
background-color: rgba(25, 33, 61, 0.4);
border: 1px solid rgba(186, 208, 241, 0.1);
border-radius: 8px;
}
.el-descriptions{
::v-deep .el-descriptions__body {
background-color: transparent;
.el-descriptions__table .el-descriptions-item__cell {
font-size: 16px;
font-weight: 500;
line-height: 20px;
padding-top: 20px;
padding-left: 25px;
color: rgba(255, 255, 255, 0.6);
}
}
}
</style>

View File

@@ -1,199 +0,0 @@
<template>
<div class="dresource-monitor">
<div class="cpu">
<div style="width:100%;height: 100%;" id="cpu"></div>
</div>
<div class="memory">
<div style="width:100%;height: 100%;" id="memory"></div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts';
import dayjs from 'dayjs'
import { getCpuResponse } from './cpuMock.js'
import { getMemoryResponse } from './memoryMock.js'
export default {
data() {
return {
nodeId: '',
cpuData: [],
cpuDataX: [],
cpuDataY: [],
memoryData: [],
memoryDataX: [],
memoryDataY: []
}
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
// this.init()
},
immediate: true
}
},
created() {
},
mounted() {
this.init()
},
methods: {
init() {
// this.cpuData = getCpuResponse.result
// this.cpuData.forEach(item => {
// this.cpuDataX.push(item[0])
// this.cpuDataY.push(item[1])
// })
// this.memoryData = getMemoryResponse.result
// this.memoryData.forEach(item => {
// this.memoryDataX.push(item[0])
// this.memoryDataY.push(item[1])
// })
// this.setCpuEcharts()
// this.setMemoryEcharts()
const reqParams = {
node_id: this.nodeId,
}
this.$axios.get(this.$http.api.getCpu, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.cpuData = res?.result
this.cpuData.forEach(item => {
this.cpuDataX.push(dayjs(item[0]).format('HH:mm:ss'))
this.cpuDataY.push(item[1])
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.setCpuEcharts()
})
this.$axios.get(this.$http.api.getMemory, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.memoryData = res?.result
this.memoryData.forEach(item => {
this.memoryDataX.push(dayjs(item[0]).format('HH:mm:ss'))
this.memoryDataY.push(item[1])
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.setMemoryEcharts()
})
},
setCpuEcharts() {
var chartDom = document.getElementById('cpu');
var myChart = echarts.init(chartDom);
var option;
option = {
// color: ['rgba(61, 255, 220, 0.2)'],
grid: {
// top: '8%',
height: '55%' // 设置折线图的高度为容器高度的百分之四十
},
xAxis: {
type: 'category',
data: this.cpuDataX,
axisLabel: {
color: '#ffffff'
}
},
yAxis: {
type: 'value',
name: 'cpu (mcore)',
nameTextStyle: {
color: '#ffffff'
},
axisLine: {
show: true // 将此处改为 true 则显示 Y 轴刻度线
// symbol: ['none', 'arrow']
},
splitLine: {
show: false
},
axisLabel: {
color: '#ffffff'
}
},
series: [
{
data: this.cpuDataY,
type: 'line',
smooth: true
}
]
}
option && myChart.setOption(option)
},
setMemoryEcharts() {
var chartDom = document.getElementById('memory');
var myChart = echarts.init(chartDom);
var option;
option = {
grid: {
top: '18%',
height: '55%' // 设置折线图的高度为容器高度的百分之四十
},
xAxis: {
type: 'category',
data: this.memoryDataX,
axisLabel: {
color: '#ffffff'
}
},
yAxis: {
type: 'value',
name: '内存 (Mi)',
nameTextStyle: {
color: '#ffffff'
},
axisLine: {
show: true // 将此处改为 true 则显示 Y 轴刻度线
// symbol: ['none', 'arrow']
},
splitLine: {
show: false
},
axisLabel: {
color: '#ffffff'
}
},
series: [
{
data: this.memoryDataY,
type: 'line',
smooth: true
}
]
}
option && myChart.setOption(option)
}
}
}
</script>
<style lang='less' scoped="scoped">
.dresource-monitor {
width: 100%;
height: 100%;
background-color: rgba(25, 33, 61, 0.4);
border: 1px solid rgba(186, 208, 241, 0.1);
border-radius: 8px;
}
.cpu {
width: 100%;
height: 50%;
}
.memory {
width: 100%;
height: 50%;
}
</style>

View File

@@ -1,91 +0,0 @@
const getCpuResponse = {
"code": 200,
"message": "success",
"result": [
[
"2024-01-06 09:33:14",
1.757
],
[
"2024-01-06 09:34:14",
1.694
],
[
"2024-01-06 09:35:14",
1.573
],
[
"2024-01-06 09:36:14",
0.877
],
[
"2024-01-06 09:37:14",
0.774
],
[
"2024-01-06 09:38:14",
0.855
],
[
"2024-01-06 09:39:14",
0.903
],
[
"2024-01-06 09:40:14",
0.929
],
[
"2024-01-06 09:41:14",
1.135
],
[
"2024-01-06 09:42:14",
1.154
],
[
"2024-01-06 09:43:14",
0.963
],
[
"2024-01-06 09:44:14",
1.195
],
[
"2024-01-06 09:45:14",
1.128
],
[
"2024-01-06 09:46:14",
1.294
],
[
"2024-01-06 09:47:14",
1.698
],
[
"2024-01-06 09:48:14",
1.136
],
[
"2024-01-06 09:49:14",
1.554
],
[
"2024-01-06 09:50:14",
1.721
],
[
"2024-01-06 09:51:14",
1.941
],
[
"2024-01-06 09:52:14",
1.594
],
[
"2024-01-06 09:53:14",
1.955
]
]
}
export { getCpuResponse }

View File

@@ -1,91 +0,0 @@
const getMemoryResponse = {
"code": 200,
"message": "success",
"result": [
[
"2024-01-07 01:08:27",
42.371
],
[
"2024-01-07 01:09:27",
42.371
],
[
"2024-01-07 01:10:27",
42.371
],
[
"2024-01-07 01:11:27",
42.371
],
[
"2024-01-07 01:12:27",
42.371
],
[
"2024-01-07 01:13:27",
42.371
],
[
"2024-01-07 01:14:27",
42.371
],
[
"2024-01-07 01:15:27",
42.383
],
[
"2024-01-07 01:16:27",
42.383
],
[
"2024-01-07 01:17:27",
42.383
],
[
"2024-01-07 01:18:27",
42.383
],
[
"2024-01-07 01:19:27",
42.383
],
[
"2024-01-07 01:20:27",
42.383
],
[
"2024-01-07 01:21:27",
42.383
],
[
"2024-01-07 01:22:27",
42.383
],
[
"2024-01-07 01:23:27",
42.383
],
[
"2024-01-07 01:24:27",
42.383
],
[
"2024-01-07 01:25:27",
42.395
],
[
"2024-01-07 01:26:27",
42.402
],
[
"2024-01-07 01:27:27",
42.41
],
[
"2024-01-07 01:28:27",
42.422
]
]
}
export { getMemoryResponse }

View File

@@ -1,304 +0,0 @@
<template>
<div class="range-config-manage" ref="appRef">
<Header
:down-loading="downLoading"
:start-loading="startLoading"
:stop-loading="stopLoading"
@download="download"
@start="start"
@end="end"
></Header>
<div class="list" >
<el-table
class="custom-table"
ref="multipleTable"
v-loading="loading"
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionChange"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="bucket_name"
label="桶名称"
min-width="100"/>
<el-table-column
align="center"
prop="file_name"
label="文件名"
min-width="250"/>
<el-table-column
align="center"
prop="size"
label="文件大小"
min-width="80"/>
<el-table-column
align="center"
prop="last_modified"
label="更新时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.last_modified | formatTime }}
</template>
</el-table-column>
</el-table>
</div>
<!-- <el-pagination
background
:current-page="page"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination> -->
</div>
</template>
<script>
import Header from './module/Header.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "File",
components:{ Header},
data(){
return{
target_id: '',
nodeId: '',
tableData: [],
selectFileNameList: [],
selectTableData: [],
loading: false,
downLoading: false,
startLoading: false,
stopLoading: false
}
},
mounted() {
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
this.init()
},
immediate: true
},
'$store.state.node.startTraffic': {
handler(newVal, oldVal) {
this.startLoading = newVal
},
immediate: true
},
'$store.state.node.endTraffic': {
handler(newVal, oldVal) {
this.stopLoading = newVal
},
immediate: true
}
},
created() {
},
methods:{
init() {
// this.tableData = getTargetsResponse?.result
const reqParams = { node_id: this.nodeId }
this.loading = true
this.$axios.get(this.$http.api.getNodeFile, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.tableData = res?.result
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
// 当选择其它角色时,弹窗填写角色窗
openRoleForm() {
this.$refs.roleForm.visible = true
},
// 下载
download() {
if (this.selectFileNameList.length <= 0) {
this.$notify({
title: '请勾选要下载的文件',
type: 'warning',
duration: 2500
})
return
}
const reqParams = {
node_id: this.nodeId,
object_file_name_list: JSON.stringify(this.selectFileNameList)
}
this.downLoading = true
this.$axios.getFile(this.$http.api.downloadNodeFile, reqParams).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载文件成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载文件失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.downLoading = false
})
},
// 开始
start() {
const reqParams = {
node_id: this.nodeId,
command: 'start'
}
// this.startLoading = true
this.$store.commit('node/setStartTraffic', true)
this.$axios.get(this.$http.api.flowStartStop, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '开始统计流量',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
this.$store.commit('node/setStartTraffic', false)
}).finally(() => {
// this.startLoading = false
})
},
// 结束
end() {
const reqParams = {
node_id: this.nodeId,
command: 'stop'
}
// this.stopLoading = true
this.$store.commit('node/setEndTraffic', true)
this.$axios.get(this.$http.api.flowStartStop, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$store.commit('node/setStartTraffic', false)
this.$notify({
title: '结束统计流量',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
setTimeout(() => {
// this.stopLoading = false
this.$store.commit('node/setEndTraffic', false)
this.init()
}, 5 * 1000);
})
},
// 处理选中的数据
handleSelectionChange(selectData) {
this.selectTableData = selectData
this.selectFileNameList = selectData.map(item => {
return item.file_name
})
},
// // 修改每页数据条数
// handleSizeChange(val) {
// console.log(`每页 ${val} 条`)
// this.page=1
// this.size=val
// this.query()
// },
// // 修改页数
// handleCurrentChange(val) {
// console.log(`当前页: ${val}`)
// this.page=val
// this.query()
// }
}
}
</script>
<style lang='less' scoped="scoped">
.el-pagination {
padding: 15px 55px 0 0 !important;
}
.custom-table {
width: 100%;
height: 100%;
}
.range-config-manage{
width: 100%;
height: 100%;
// display: flex;
// flex-direction: column;
// justify-content: flex-start;
.list{
width: 100%;
height: 87%;
background-color: rgba(25, 33, 61, 0.4);
border: 1px solid rgba(186, 208, 241, 0.1);
border-radius: 8px;
// margin-left: 2.5%;
// overflow-y: auto;
// overflow-y: scroll;
// overflow-x: hidden;
// border: none;
}
.list::-webkit-scrollbar {
width: 0px; /* 隐藏滚动条 */
height: 0px;
background-color: transparent; /* 让背景透明 */
}
/* 隐藏火狐浏览器滚动条 */
@-moz-document url-prefix() {
.trackSource {
scrollbar-width: none;
}
}
// 遮罩层
.mask{
position: fixed; /*将元素设置为固定定位*/
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /*通过rgba函数来控制遮罩层的透明度*/
display: none; /*将元素隐藏*/
}
}
</style>

View File

@@ -1,26 +0,0 @@
const getTargetsResponse = {
"code": 200,
"message": "success",
"result": [
{
"bucket_name": "target-1-da-15",
"file_name": "target_1_da_15_2023_07_31_23_58_04.pcap",
"last_modified": "2023-07-31T15:58:40.158000+00:00",
"size": "3.80KiB"
},
{
"bucket_name": "target-1-da-15",
"file_name": "target_1_da_15_2023_08_01_00_04_07.pcap",
"last_modified": "2023-07-31T16:07:09.377000+00:00",
"size": "21.7KiB"
},
{
"bucket_name": "target-1-da-15",
"file_name": "target_1_da_15_2023_08_01_00_13_41.pcap",
"last_modified": "2023-07-31T16:16:08.605000+00:00",
"size": "20.6KiB"
}
]
}
export { getTargetsResponse }

View File

@@ -1,74 +0,0 @@
<template>
<div class="head">
<span class="title">节点流量采集文件</span>
<el-button :loading="downLoading" type="primary" @click="download">下载</el-button>
<el-button :loading="startLoading" type="primary" @click="start">开始</el-button>
<el-button :loading="stopLoading" type="primary" @click="end">结束</el-button>
</div>
</template>
<script>
export default {
name: 'Header',
props: ['startLoading', 'stopLoading', 'downLoading'],
data() {
return {
role: '',
has_deployed: ''
}
},
methods: {
// 下载
download() {
this.$emit('download')
},
// 开始
start() {
this.$emit('start')
},
// 结束
end() {
this.$emit('end')
}
}
}
</script>
<style lang="less" scoped>
.head{
width: 100%;
height: 12%;
margin-top: 1%;
text-align: right;
/*background-color: #5daf34;*/
.block{
display: inline-block;
margin-left: 2%;
}
.input{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
.title {
margin-right: 60%;
}
}
</style>

View File

@@ -1,80 +0,0 @@
<template>
<div class="container">
<div class="card-detail">
<Detail></Detail>
</div>
<div class="card-file">
<File></File>
</div>
<div class="card">
<Log></Log>
</div>
<div class="card">
<Console></Console>
</div>
</div>
</template>
<script>
import Detail from './detail/index.vue'
import File from './file/index.vue'
import Log from './log/index.vue'
import Console from './console/index.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "NodeDetail",
components: { Detail, File, Log, Console},
data(){
return{
nodeId: '',
nodeDetail: {}
}
},
mounted() {
},
// watch: {
// '$store.state.range.nodeId': {
// handler(newVal, oldVal) {
// this.nodeId = newVal
// this.init()
// },
// immediate: true
// }
// },
created() {
},
methods:{
// init() {}
}
}
</script>
<style lang='less' scoped="scoped">
.container{
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-around;
align-content: space-around;
background-image:url('../../../img/background/backgroundFourCorner.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% auto; /* 宽度为100%,高度自适应保持宽高比 */
.card{
width: 48%;
height: 46%;
background-image:url('../../../img/backgroundFourCorner.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
}
.card-detail{
width: 48%;
height: 46%;
}
.card-file{
width: 48%;
height: 46%;
}
}
</style>

View File

@@ -1,227 +0,0 @@
<template>
<div :class="['range-config-manage', {'is--maximize': isFullscreen}]" ref="appRef">
<Header
:is-fullscreen="isFullscreen"
:fullscreen-icon="fullscreenIcon"
:down-loading="downLoading"
:close-icon="closeIcon"
@query="query"
@download="download"
@zoomEvent="zoomEvent"
></Header>
<div class="list">
<el-table
class="custom-table"
ref="multipleTable"
v-loading="loading"
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
>
<el-table-column
align="center"
prop="id"
label="id"
min-width="50"/>
<el-table-column
align="left"
prop="content"
label="日志内容"
min-width="500"
show-overflow-tooltip/>
</el-table>
</div>
</div>
</template>
<script>
import Header from './module/Header.vue'
import { getTargetsResponse } from './mock.js'
export default {
name: "File",
components:{ Header},
data(){
return{
fullscreenIcon: 'fullscreen',
isFullscreen: false,
target_id: '',
nodeId: '',
downLoading: false,
tableData: [],
inverterMonTimer: null,
loading: false,
closeIcon: false
}
},
mounted() {
this.closeIcon = this.$route.params.closeIcon
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
this.init()
},
immediate: true
}
},
created() {
this.inverterMonTimer = setInterval(() => {
this.init()
}, 5 * 1000)
},
beforeDestroy() {
if (this.inverterMonTimer) {
clearInterval(this.inverterMonTimer);
this.inverterMonTimer = null;
}
},
methods:{
zoomEvent() {
this.isFullscreen = !this.isFullscreen
this.fullscreenIcon = this.isFullscreen ? 'narrow' : 'fullscreen'
},
init(params={}) {
// const responseData = getTargetsResponse?.result
// this.tableData = responseData.map((item, index) => {
// return {
// id: index,
// content: item
// }
// })
// TODO: 暂时注释接口
const reqParams = {
node_id: this.nodeId,
...params
}
this.loading = true
this.$axios.get(this.$http.api.getLog, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const responseData = res?.result
this.tableData = responseData.map((item, index) => {
return {
id: index,
content: item
}
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
query(params) {
// 清除日志轮询器
if (this.inverterMonTimer) {
clearInterval(this.inverterMonTimer);
this.inverterMonTimer = null;
}
// 立即查询一次
this.init(params)
// 开启新的日志轮询器
this.inverterMonTimer = setInterval(() => {
this.init(params)
}, 5 * 1000)
},
// 下载
download() {
this.downLoading = true
const reqParams = { node_id: this.nodeId }
this.$axios.getFile(this.$http.api.downloadLog, reqParams).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载日志成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载日志失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.downLoading = false
})
},
// // 修改每页数据条数
// handleSizeChange(val) {
// console.log(`每页 ${val} 条`)
// this.page=1
// this.size=val
// this.query()
// },
// // 修改页数
// handleCurrentChange(val) {
// console.log(`当前页: ${val}`)
// this.page=val
// this.query()
// }
}
}
</script>
<style lang='less' scoped="scoped">
.is--maximize {
position: fixed;
top: 9%;
left: 10%;
width: 90% !important;
height: 91% !important;
padding: 0.5em 1em;
// background-color: #17234e;
background: url(../../../../img/background/bgMain.svg);
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-position: center; /* 居中显示 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
z-index: 1000;
}
.el-pagination {
padding: 15px 55px 0 0 !important;
}
.custom-table {
width: 100%;
height: 100%;
border-radius: 0px;
background-color: transparent !important;
}
.range-config-manage{
width: 100%;
height: 100%;
.list{
width: 100%;
height: calc(100% - 85px);
}
// 遮罩层
.mask{
position: fixed; /*将元素设置为固定定位*/
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /*通过rgba函数来控制遮罩层的透明度*/
display: none; /*将元素隐藏*/
}
}
</style>

View File

@@ -1,12 +0,0 @@
const getTargetsResponse ={
"code": 200,
"message": "success",
"result": [
"Jan 02 18:36:44.000 [notice] Tor 0.4.8.10 opening new log file.\n",
"Jan 02 18:36:44.000 [info] Tor 0.4.8.10 opening new log file.\n",
"Jan 02 18:36:44.000 [warn] Tor 0.4.8.10 opening new log file.\n",
"Jan 02 18:36:44.000 [warn] Tor 0.4.8.10 opening new log file.\n",
]
}
export { getTargetsResponse }

View File

@@ -1,122 +0,0 @@
<template>
<div class="head">
<span class="title">日志流展示</span>
<div class="role-select">
<el-select v-model="level" clearable placeholder="全部" @change="query">
<el-option
v-for="item in levelDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<el-button :loading="downLoading" class="role-select" type="primary" @click="download">下载</el-button>
<span class="icon-span" slot="label">
<i v-if="closeIcon" class="el-icon-close icon-zoom" @click="backNodeList"></i>
<svg-icon v-else class="icon-zoom" :icon-class="fullscreenIcon" @click="zoomEvent"></svg-icon>
</span>
</div>
</template>
<script>
export default {
name: 'Header',
props: {
isFullscreen: {
typeof: Boolean
},
fullscreenIcon: {
typeof: String
},
downLoading: {
typeof: Boolean
},
closeIcon: {
typeof:Boolean
}
},
data() {
return {
level: '',
levelDict:[
{label: 'info', value: 'info'},
{label: 'notice', value: 'notice'},
{label: 'warn', value: 'warn'}
],
}
},
methods: {
zoomEvent() {
this.$emit('zoomEvent')
// this.fullscreenIcon = this.isFullscreen ? 'el-icon-full-screen' : 'el-icon-rank'
},
// 查询
query() {
let params = {}
if (this.level) {
params.level = this.level
}
this.$emit('query', params)
},
// 下载
download() {
this.$emit('download')
},
backNodeList() {
this.$router.push({ name: 'rangeNodeManage' })
}
}
}
</script>
<style lang="less" scoped>
.head{
width: 100%;
height: 50px;
margin-top: 20px;
text-align: right;
/*background-color: #5daf34;*/
.block{
display: inline-block;
margin-left: 2%;
}
.input{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
.title {
margin-right: 45%;
}
::v-deep .role-select{
display: inline-block;
margin-right: 15px;
}
.icon-span {
padding: 12px 20px;
.icon-zoom {
font-size: 30px;
color: #02DDEA;
margin: 0 15px;
}
}
}
</style>

View File

@@ -1,38 +0,0 @@
const getTargetsResponse = {
"code": 200,
"message": "success",
"result": {
"setting_id": 70,
"traffic_collect_task_id": null,
"id": 90,
"ip": "148.113.2.0",
"pod_name": "target-11-relay-90-568f44566f-rvbl8",
"nick_name": "relaykmuhkwkdjy",
"status": "complete",
"onion": null,
"create_time": "2024-01-02T18:36:37",
"complete_time": "2024-01-02T18:48:20",
"role": null,
"owner_setting": {
"role": "relay",
"image_id": 34,
"replicas": "1",
"bandwidth": 10,
"memory": 100,
"service": null,
"tor_version": "4.8.9",
"create_time": null,
"update_time": null,
"out_port": null,
"socks_port": 19001,
"control_port": 19002,
"or_port": 7000,
"dir_port": 9030,
"use_network": {
"cidr": "148.113.2.0/24"
}
}
}
}
export { getTargetsResponse }

View File

@@ -1,146 +0,0 @@
<template>
<div class="range-node-manage">
<div class="container">
<div class="header">
<el-select class="range-select" v-model="target_id" placeholder="全部靶场" @change="changeRange">
<el-option
v-for="item in rangeDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="map-container">
<WorldMap></WorldMap>
<TopologyMap></TopologyMap>
</div>
<div class="list-container">
<NodeList></NodeList>
</div>
</div>
</div>
</template>
<script>
import WorldMap from './worldMap'
import TopologyMap from './topologyMap'
import NodeList from './nodeList'
export default {
name: 'rangeNodeManage',
components: {
NodeList,
WorldMap,
TopologyMap
},
data() {
return {
target_id: '',
rangeDict: []
}
},
watch: {
'$store.state.range.targetId': {
handler(newVal, oldVal) {
this.target_id = newVal
},
immediate: true
}
},
created() {
this.getRangeDict()
},
methods: {
// 改变靶场
changeRange() {
this.$store.commit('range/setTargetId', this.target_id)
},
// 获取靶场列表字典
getRangeDict() {
const reqParams = {
page: 1,
size: 99,
}
this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.rangeDict = res?.result?.items.map(item => {
return {
label: item.target_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.rangeDict.unshift({label: '全部靶场', value: ''})
})
},
}
}
</script>
<style lang='less' scoped>
.range-node-manage {
width: 100%;
height: 100%;
position: relative;
.container {
width: 95%;
height: 95%;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../img/backgroundFourCorner.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
padding: 1.5%;
}
}
.header {
height: 7%;
width: 100%;
text-align: left;
.range-select {
// position: absolute;
// top: 12px;
// right: 140px;
::v-deep .el-input {
width: 60%;
.el-input__inner {
background-color: transparent !important;
border-color: transparent;
border-radius: 0;
color: #FFFFFF;
}
/* select去除竖线 */
.el-input__suffix::before {
content: "";
width: 0;
height: 0;
margin: 0;
position: absolute;
}
/*select的上下箭头图标样式*/
.el-select__caret {
color: rgba(2, 221, 234, 0.9);
}
}
}
}
.map-container{
height: 43%;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.list-container{
height: 50%;
width: 100%;
}
</style>

View File

@@ -1,95 +0,0 @@
<template>
<div class="role-dialog" v-if="visible">
<i class="el-icon-close" style="float: right; padding-right: 7%;padding-top: 2.8%" @click="close"></i>
<div class="fbs">
<span style="margin-right: 3%">文件名</span>
<el-input
size="mini"
:value="value"
placeholder="请输入内容"
style="width: 50%"
v-bind="$attrs"
v-on="$listeners">
</el-input>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="submit">确认</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RoleForm',
props: {
value: {
typeof: String,
require: true,
default: ''
}
},
data() {
return {
visible: false
}
},
watch: {
value: {
handler(val) {
this.$emit('input', val)
}
}
},
methods: {
close() {
// document.querySelector('.mask').style.display = 'none'
this.visible = false
},
submit() {
// document.querySelector('.mask').style.display = 'none'
this.visible = false
this.$emit('makeDirSubmit')
}
}
}
</script>
<style lang="less" scoped>
.role-dialog{
z-index: 998;
width: 300px;
height: 120px;
position: absolute; /* 绝对定位 */
top: 100px; /* 向下偏移50% */
left: 150px; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../img/jbpzxybqr.png');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.fbs{
width: 100%;
float: left;
margin-top: 3%;
text-align: center;
}
.anDiv{
width: 100%;
float: left;
margin-top: 5%;
text-align: center;
.glBut{
width: 50px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
</style>

View File

@@ -1,536 +0,0 @@
<template>
<div class="content">
<el-tree
ref="tree"
class="tree-section"
lazy
node-key="id"
:data="treeData"
:props="defaultProps"
:load="loadNode"
@node-click="handleNodeClick"
v-dragresize="dragConfig"
>
</el-tree>
<div class="tree-content">
<div style="text-align:left; width: 100%; padding: 10px;border: 1px solid #BAD0F11A;">
<span>
{{ dirTitle }}
</span>
</div>
<div style="clear:both;text-align:left; padding: 10px;border-bottom: 1px solid #BAD0F11A;">
<img src="../../../../img/icon/file-icon.svg" alt="" style="width: 18px; height: 18px;">
{{ currentFileName }}
</div>
<ul style="clear:both;" class="tree-content-ul">
<li v-for="fileItem in fileList" :key="fileItem.name">
<el-radio v-model="filename" :label="fileItem.name" class="file-info">{{ '' }}</el-radio>
<span class="file-info">{{ fileItem.name }}</span>
<i class="el-icon-document file-info" style="font-size: 20px"></i>
<span class="file-info">{{ fileItem.size }}</span>
<span class="file-info">{{ fileItem.time }}</span>
</li>
</ul>
<div class="empty-box"></div>
</div>
<!-- <ul
v-show="visible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li>
<li @click="download">下载</li>
<li @click="del">删除</li>
</ul> -->
<!-- <ul
v-show="uploadVisible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li>
</ul> -->
<!-- <div v-show="showTreeMenu" class="treeMenu">
<div @click="makeDir">新建文件夹</div>
<div @click="delDir">删除文件夹</div>
</div> -->
<DirNameForm
ref="dirNameForm"
v-model="dirName"
@makeDirSubmit="makeDirSubmit">
</DirNameForm>
</div>
</template>
<script>
import DirNameForm from './DirNameForm.vue'
export default {
components: { DirNameForm },
props: ['isFullscreen'],
data() {
return{
visible: false,
uploadVisible: false,
top: 0,
left: 0,
nodeId: '',
dirTitle: '>',
currentFileName: '',
fileList: [],
selectFile: {},
selectNode: {path: '/'},
rightClickNode: {},
defaultProps: {
children: 'children',
label: 'name',
},
showTreeMenu: false, // 文件夹数据
contextNode: {}, // 文件夹数据
dirName: '', // 新建文件名
treeData: [],
filename: ''
}
},
computed: {
dragConfig() {
return [
{
dragBorder: "right",
setCssProperty: "width",
},
];
},
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
// this.getCurrentDirFile('/')
},
immediate: true
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
},
uploadVisible(value) {
if (value) {
document.body.addEventListener('click', this.closeUploadMenu)
} else {
document.body.removeEventListener('click', this.closeUploadMenu)
}
},
},
methods: {
// upload(e) {
// this.$emit('selectUploadFile', {path: this.selectNode.path, file: e.target.files[0]})
// const params = {
// node_id : this.nodeId,
// current_dir: this.selectNode.path,
// file: e.target.files[0]
// }
// const submitForm = new FormData()
// for(const key in params) {
// submitForm.append(key, params[key])
// }
// this.$axios.postFormData(this.$http.api.uploadFile, submitForm).then(res => {
// if (res.code == 200 || res.code == "OK") {
// this.$notify({
// title: '上传文件成功',
// type: 'success',
// duration: 2500
// })
// // 更新当前路径下的文件信息
// this.handleNodeClick(this.selectNode)
// this.setCurrentHighlight(this.selectNode.$treeNodeId);
// }
// }).catch(err => {
// console.log(err)
// })
// },
// download() {
// // this.downLoading = true
// const reqParams = {
// master_node_id : this.nodeId,
// current_dir: this.selectNode.path,
// filename: this.selectFile.name
// }
// this.$axios.getFile(this.$http.api.batchDownload, reqParams).then(res => {
// if (res.status == 200 || res.statusText == "OK") {
// const { data, headers } = res
// const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// // 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
// //const blob = new Blob([JSON.stringify(data)], ...)
// const blob = new Blob([data], {type: headers['content-type']})
// let dom = document.createElement('a')
// let url = window.URL.createObjectURL(blob)
// dom.href = url
// dom.download = decodeURI(fileName)
// dom.style.display = 'none'
// document.body.appendChild(dom)
// dom.click()
// dom.parentNode.removeChild(dom)
// window.URL.revokeObjectURL(url)
// this.$notify({
// title: '下载文件成功',
// type: 'success',
// duration: 2500
// })
// } else {
// this.$notify({
// title: '下载文件失败',
// type: 'success',
// duration: 2500
// })
// }
// }).catch(err => {
// console.log(err)
// }).finally(() => {
// // this.downLoading = false
// })
// },
del() {
const params = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
filename: this.selectFile.name
}
this.$axios.delete(this.$http.api.delFile, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 更新当前路径下的文件信息
this.handleNodeClick(this.selectNode)
// console.log('选中了节点id===', this.selectNode.$treeNodeId)
this.setCurrentHighlight(this.selectNode.$treeNodeId);
// this.init()
}
}).catch(err => {
console.log(err)
})
},
// 设置当前选中高亮
setCurrentHighlight (id) {
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id)
})
},
openMenu(fileItem, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 180 // fix 位置bug
} else {
this.top = e.clientY - 160 // fix 位置bug
}
this.uploadVisible = false
this.visible = true
this.selectFile = fileItem
},
closeMenu() {
this.visible = false
},
openUploadMenu(e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 120 // fix 位置bug
} else {
this.top = e.clientY - 160 // fix 位置bug
}
this.visible = false
this.uploadVisible = true
},
closeUploadMenu() {
this.uploadVisible = false
},
// 获取当前路径下文件
getCurrentDirFile(currentDir) {
const reqParams = { node_id: this.nodeId, current_dir: currentDir }
return this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const dirs = res?.result?.object?.dir || []
this.fileList = res?.result?.object?.file || []
// console.log('请求回来files===', files)
const nodeData = dirs.map(dir => {
return {
name: dir.name,
path: currentDir === '/' ? currentDir + dir.name : `${currentDir}/${dir.name}`,
fileList: []
}
})
// console.log('请求回来nodeData====', nodeData)
return nodeData
}
}).catch(err => {
console.log(err)
})
},
// 点击加载子节点
async loadNode(node, reslove) {
// console.log('执行了loadNode', node)
if (node.level == 0) {
let nodeData = await this.getCurrentDirFile('/')
// console.log('初始化数据===', nodeData)
// this.selectNode = nodeData.fileList
return reslove(nodeData)
} else {
let nodeData = await this.getCurrentDirFile(node.data.path)
// 判断是否为最底层节点 根据自己接口返回的数据判断即可
nodeData.forEach(item => {
item.hasChildren == false ? item.lastNode = ture : null
});
// this.selectNode = nodeData.fileList
return reslove(nodeData)
}
},
handleNodeClick(node) {
// this.fileList = []
// console.log('选中了节点===', node)
// console.log('选中了节点id===', node.$treeNodeId)
this.selectNode = node
this.dirTitle = node.path.split('/').join('>')
this.currentFileName = node.name
// this.fileList = node.fileList
const reqParams = { node_id: this.nodeId, current_dir: node.path }
this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.fileList = res?.result?.object?.file || []
}
}).catch(err => {
console.log(err)
})
},
openTreeMenu(event, data, node, target) {
// console.log(event, data, node, target)
this.showTreeMenu = true // 显示菜单
this.contextNode = data // 存储数据
// console.log(this.contextNode, 'contextNode')
// console.log(node, 'rightClickNode=======')
// console.log(target, 'target')
this.rightClickNode = node
document.querySelector('.treeMenu').setAttribute('style',`top:${event.clientY + 30}px;left:${event.clientX + 30}px;`)
document.addEventListener('click', this.closeTreeMenu)
document.addEventListener('contextmenu', this.closeTreeMenu)
},
closeTreeMenu() {
this.showTreeMenu = false // 关闭菜单
document.removeEventListener('click', this.closeTreeMenu)
document.removeEventListener('contextmenu', this.closeTreeMenu)
},
// 当新建文件夹时,先填写名字
openDirNameForm() {
this.$refs.dirNameForm.visible = true
},
// 新建文件夹
makeDir() {
this.openDirNameForm()
},
// 新建文件夹接口提交
makeDirSubmit() {
const reqParams = { node_id: this.nodeId, current_dir: this.contextNode.path, new_dir_name: this.dirName }
this.$axios.get(this.$http.api.makeDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '新建文件夹成功',
type: 'success',
duration: 2500
})
// 页面增加子节点
let id = Math.ceil(Math.random() * 1000000)
var addNode = {
path: this.contextNode.path + '/' + this.dirName,
name: this.dirName,
fileList: []
// id: id,
// name: this.dirName,
// data: {
// path: this.contextNode.path,
// name: this.dirName,
// fileList: []
// }
}
// console.log(addNode, 'addNode======')
this.$refs.tree.append(addNode, this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
},
// 删除文件夹
delDir() {
const index = this.contextNode.path.lastIndexOf('/')
const path = this.contextNode.path.substring(0, index);
const params = {
node_id : this.nodeId,
current_dir: path,
dir_name: this.contextNode.name
}
this.$axios.delete(this.$http.api.delDir, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 页面删除子节点
this.$refs.tree.remove(this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang='less' scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
color: #ffffff;
.tree-section {
width: 25%;
height: 100%;
// background-color: #1A2648;
background: transparent;
border: 1px solid #BAD0F11A;
color: #06F7FF;
overflow-y: auto;
::v-deep .el-tree-node__content:hover {
background-color: rgba(14, 61, 138, 0.3) !important;
}
::v-deep .el-tree-node.is-current > .el-tree-node__content {
background-color: rgba(14, 61, 138, 0.3) !important;
}
}
.tree-content {
width: 75%;
height: 100%;
display: flex;
flex-direction: column;
.tree-content-ul {
width: 100%;
background-color: rgba(25, 33, 61, 0.4);
overflow-y: auto;
li {
display: flex;
flex-direction: row;
justify-content: space-around;
border-bottom: 1px solid #BAD0F11A;
padding: 5px;
.file-info {
flex: 1 1 100px;
}
}
}
.empty-box {
flex: 1;
}
}
}
.contextmenu {
margin: 0;
border-radius: 6px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
background: #0E3D8A;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
font-size: 12px;
font-weight: 400;
color: #06F7FF;
// box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
li {
margin: 0;
padding: 7px 30px;
cursor: pointer;
&:hover {
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
}
}
.file-input {
display: none;
}
}
.treeMenu {
position: fixed;
z-index: 99999;
top: 50%;
left: 50%;
color: #06F7FF;
background-color: #0E3D8A;
overflow: hidden;
border-radius: 5px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
div{
padding: 7px 30px;
box-sizing: border-box;
//width: 50px;
text-align: center;
}
div:hover{
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
cursor: pointer;
}
}
</style>

View File

@@ -1,98 +0,0 @@
<template>
<el-table
class="main-table styleTable"
ref="mainTable"
height="500px"
style="width: 100%;"
:data="nodeList"
tooltip-effect="dark"
highlight-current-row
>
<el-table-column label="" width="80" align="center">
<template slot-scope="scope">
<el-radio v-model="master_node_id" :label="scope.row.id" @input="handleSelectionMain(scope.row)">{{ "" }}</el-radio>
</template>
</el-table-column>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<!-- <template slot-scope="scope">
{{ scope.row.has_deployed ? '已部署' : '未部署' }}
</template> -->
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatTime }}
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
name: 'MainNode',
props: {
nodeList: {
typeof: Array,
require: true
}
},
data() {
return {
master_node_id: ''
}
},
methods: {
// 选择主节点
handleSelectionMain(row) {
this.$emit('selectMainNode', row)
},
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,98 +0,0 @@
<template>
<el-table
class="main-table styleTable"
ref="operateTable"
height="500px"
style="width: 100%;"
:data="nodeList"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionOperateNode"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<!-- <template slot-scope="scope">
{{ scope.row.has_deployed ? '已部署' : '未部署' }}
</template> -->
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatTime }}
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
name: 'OperateNode',
props: {
nodeList: {
typeof: Array,
require: true
}
},
data() {
return {
}
},
methods: {
// 选择被操作节点
handleSelectionOperateNode(selectRows) {
this.$emit('selectOperateNode', selectRows)
},
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,520 +0,0 @@
<template>
<div class="content">
<el-tree
ref="tree"
class="tree-section"
lazy
node-key="id"
:data="treeData"
:props="defaultProps"
:load="loadNode"
@node-click="handleNodeClick"
v-dragresize="dragConfig"
>
</el-tree>
<div class="tree-content">
<div style="text-align:left; width: 100%; padding: 10px;border: 1px solid #BAD0F11A;">
<span>
{{ dirTitle }}
</span>
</div>
<div style="clear:both;text-align:left; padding: 10px;border-bottom: 1px solid #BAD0F11A;">
<img src="../../../../img/icon/file-icon.svg" alt="" style="width: 18px; height: 18px;">
{{ currentFileName }}
</div>
<ul style="clear:both;" class="tree-content-ul">
<li v-for="fileItem in fileList" :key="fileItem.name">
<i class="el-icon-document file-info" style="font-size: 20px"></i>
<span class="file-info">{{ fileItem.name }}</span>
<span class="file-info">{{ fileItem.size }}</span>
<span class="file-info">{{ fileItem.time }}</span>
</li>
</ul>
<!-- <div class="empty-box" @contextmenu.prevent="openUploadMenu($event)"></div> -->
</div>
<ul
v-show="visible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<!-- <li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li> -->
<!-- <li @click="download">下载</li>
<li @click="del">删除</li> -->
</ul>
<!-- <ul
v-show="uploadVisible"
:style="{left:left+'px',top:top+'px'}"
class="contextmenu"
>
<li>
<label
class="button"
for="file-input"
>
<span>上传</span>
<input
type="file"
class="file-input"
id="file-input"
@change="upload"
/>
</label>
</li>
</ul> -->
<!-- <div v-show="showTreeMenu" class="treeMenu">
<div @click="makeDir">新建文件夹</div>
<div @click="delDir">删除文件夹</div>
</div> -->
<DirNameForm
ref="dirNameForm"
v-model="dirName"
@makeDirSubmit="makeDirSubmit">
</DirNameForm>
</div>
</template>
<script>
import DirNameForm from './DirNameForm.vue'
export default {
components: { DirNameForm },
props: ['isFullscreen'],
data() {
return{
visible: false,
uploadVisible: false,
top: 0,
left: 0,
nodeId: '',
dirTitle: '>',
currentFileName: '',
fileList: [],
selectFile: {},
selectNode: {path: '/'},
rightClickNode: {},
defaultProps: {
children: 'children',
label: 'name',
},
showTreeMenu: false, // 文件夹数据
contextNode: {}, // 文件夹数据
dirName: '', // 新建文件名
treeData: []
}
},
computed: {
dragConfig() {
return [
{
dragBorder: "right",
setCssProperty: "width",
},
];
},
},
watch: {
'$store.state.range.nodeId': {
handler(newVal, oldVal) {
this.nodeId = newVal
// this.getCurrentDirFile('/')
},
immediate: true
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
},
uploadVisible(value) {
if (value) {
document.body.addEventListener('click', this.closeUploadMenu)
} else {
document.body.removeEventListener('click', this.closeUploadMenu)
}
},
},
methods: {
// upload(e) {
// this.$emit('selectUploadFile', {path: this.selectNode.path, file: e.target.files[0]})
// const params = {
// node_id : this.nodeId,
// current_dir: this.selectNode.path,
// file: e.target.files[0]
// }
// },
updateCurrentPath() {
this.handleNodeClick(this.selectNode)
this.setCurrentHighlight(this.selectNode.$treeNodeId);
},
download() {
// this.downLoading = true
const reqParams = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
filename: this.selectFile.name
}
this.$axios.getFile(this.$http.api.downloadFile, reqParams).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载文件成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载文件失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
// this.downLoading = false
})
},
del() {
const params = {
node_id : this.nodeId,
current_dir: this.selectNode.path,
filename: this.selectFile.name
}
this.$axios.delete(this.$http.api.delFile, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 更新当前路径下的文件信息
this.handleNodeClick(this.selectNode)
// console.log('选中了节点id===', this.selectNode.$treeNodeId)
this.setCurrentHighlight(this.selectNode.$treeNodeId);
// this.init()
}
}).catch(err => {
console.log(err)
})
},
// 设置当前选中高亮
setCurrentHighlight (id) {
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id)
})
},
openMenu(fileItem, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 180 // fix 位置bug
} else {
this.top = e.clientY - 160 // fix 位置bug
}
this.uploadVisible = false
this.visible = true
this.selectFile = fileItem
},
closeMenu() {
this.visible = false
},
openUploadMenu(e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 60 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
if (this.isFullscreen) {
this.top = e.clientY - 120 // fix 位置bug
} else {
this.top = e.clientY - 160 // fix 位置bug
}
this.visible = false
this.uploadVisible = true
},
closeUploadMenu() {
this.uploadVisible = false
},
// 获取当前路径下文件
getCurrentDirFile(currentDir) {
const reqParams = { node_id: this.nodeId, current_dir: currentDir }
return this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
const dirs = res?.result?.object?.dir || []
this.fileList = res?.result?.object?.file || []
// console.log('请求回来files===', files)
const nodeData = dirs.map(dir => {
return {
name: dir.name,
path: currentDir === '/' ? currentDir + dir.name : `${currentDir}/${dir.name}`,
fileList: []
}
})
// console.log('请求回来nodeData====', nodeData)
return nodeData
}
}).catch(err => {
console.log(err)
})
},
// 点击加载子节点
async loadNode(node, reslove) {
// console.log('执行了loadNode', node)
if (node.level == 0) {
let nodeData = await this.getCurrentDirFile('/')
// console.log('初始化数据===', nodeData)
// this.selectNode = nodeData.fileList
return reslove(nodeData)
} else {
let nodeData = await this.getCurrentDirFile(node.data.path)
// 判断是否为最底层节点 根据自己接口返回的数据判断即可
nodeData.forEach(item => {
item.hasChildren == false ? item.lastNode = ture : null
});
// this.selectNode = nodeData.fileList
return reslove(nodeData)
}
},
handleNodeClick(node) {
// this.fileList = []
// console.log('选中了节点===', node)
// console.log('选中了节点id===', node.$treeNodeId)
this.selectNode = node
this.dirTitle = node.path.split('/').join('>')
this.currentFileName = node.name
// this.fileList = node.fileList
const reqParams = { node_id: this.nodeId, current_dir: node.path }
this.$axios.get(this.$http.api.getCurrentDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.fileList = res?.result?.object?.file || []
}
}).catch(err => {
console.log(err)
})
},
openTreeMenu(event, data, node, target) {
// console.log(event, data, node, target)
this.showTreeMenu = true // 显示菜单
this.contextNode = data // 存储数据
// console.log(this.contextNode, 'contextNode')
// console.log(node, 'rightClickNode=======')
// console.log(target, 'target')
this.rightClickNode = node
document.querySelector('.treeMenu').setAttribute('style',`top:${event.clientY + 30}px;left:${event.clientX + 30}px;`)
document.addEventListener('click', this.closeTreeMenu)
document.addEventListener('contextmenu', this.closeTreeMenu)
},
closeTreeMenu() {
this.showTreeMenu = false // 关闭菜单
document.removeEventListener('click', this.closeTreeMenu)
document.removeEventListener('contextmenu', this.closeTreeMenu)
},
// 当新建文件夹时,先填写名字
openDirNameForm() {
this.$refs.dirNameForm.visible = true
},
// 新建文件夹
makeDir() {
this.openDirNameForm()
},
// 新建文件夹接口提交
makeDirSubmit() {
const reqParams = { node_id: this.nodeId, current_dir: this.contextNode.path, new_dir_name: this.dirName }
this.$axios.get(this.$http.api.makeDir, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '新建文件夹成功',
type: 'success',
duration: 2500
})
// 页面增加子节点
let id = Math.ceil(Math.random() * 1000000)
var addNode = {
path: this.contextNode.path + '/' + this.dirName,
name: this.dirName,
fileList: []
// id: id,
// name: this.dirName,
// data: {
// path: this.contextNode.path,
// name: this.dirName,
// fileList: []
// }
}
// console.log(addNode, 'addNode======')
this.$refs.tree.append(addNode, this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
},
// 删除文件夹
delDir() {
const index = this.contextNode.path.lastIndexOf('/')
const path = this.contextNode.path.substring(0, index);
const params = {
node_id : this.nodeId,
current_dir: path,
dir_name: this.contextNode.name
}
this.$axios.delete(this.$http.api.delDir, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除文件成功',
type: 'success',
duration: 2500
})
// 页面删除子节点
this.$refs.tree.remove(this.rightClickNode)
}
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang='less' scoped="scoped">
.content {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
color: #ffffff;
.tree-section {
width: 25%;
height: 100%;
// background-color: #1A2648;
background: transparent;
border: 1px solid #BAD0F11A;
color: #06F7FF;
overflow-y: auto;
::v-deep .el-tree-node__content:hover {
background-color: rgba(14, 61, 138, 0.3) !important;
}
::v-deep .el-tree-node.is-current > .el-tree-node__content {
background-color: rgba(14, 61, 138, 0.3) !important;
}
}
.tree-content {
width: 75%;
height: 100%;
display: flex;
flex-direction: column;
.tree-content-ul {
width: 100%;
background-color: rgba(25, 33, 61, 0.4);
overflow-y: auto;
li {
display: flex;
flex-direction: row;
justify-content: space-around;
border-bottom: 1px solid #BAD0F11A;
padding: 5px;
.file-info {
flex: 1 1 100px;
}
}
}
.empty-box {
flex: 1;
}
}
}
.contextmenu {
margin: 0;
border-radius: 6px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
background: #0E3D8A;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
font-size: 12px;
font-weight: 400;
color: #06F7FF;
// box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
li {
margin: 0;
padding: 7px 30px;
cursor: pointer;
&:hover {
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
}
}
.file-input {
display: none;
}
}
.treeMenu {
position: fixed;
z-index: 99999;
top: 50%;
left: 50%;
color: #06F7FF;
background-color: #0E3D8A;
overflow: hidden;
border-radius: 5px;
border: 0.5px solid rgba(6, 247, 255, 0.20);
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.10), 0px 8px 10px 1px rgba(0, 0, 0, 0.06), 0px 3px 14px 2px rgba(0, 0, 0, 0.05);
div{
padding: 7px 30px;
box-sizing: border-box;
//width: 50px;
text-align: center;
}
div:hover{
border-radius: 3px;
background: rgba(6, 247, 255, 0.10);
cursor: pointer;
}
}
</style>

View File

@@ -1,445 +0,0 @@
<template>
<div class="range-config-manage" ref="appRef">
<Header
:range-dict="rangeDict"
@query="query"
@batchConsole="batchConsole"
@batchDownload="batchDownload"
@batchUpload="batchUpload"
@batchCollectTraffic="batchCollectTraffic"
></Header>
<div class="list" >
<el-table
class="custom-table"
ref="multipleTable"
v-loading="loading"
element-loading-text="加载中..."
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
<span style="padding-left: 5px;">{{ roleDict[scope.row.role] || scope.row.role }}</span>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<template slot-scope="scope">
<div class="deploy-status">
<svg-icon :icon-class="getStatus(scope.row.status).class"></svg-icon>
<span :class="getStatus(scope.row.status).class">{{ getStatus(scope.row.status).label }}</span>
</div>
</template>
</el-table-column>
<el-table-column
align="center"
prop="collecting"
label="正在采集流量"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.collecting ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="150"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="150"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
min-width="200"
>
<template slot-scope="scope">
<el-button type="text" size="medium" :disabled="scope.row.status==='pending' || !roleDict[scope.row.role]" @click="console(scope.row)">控制台</el-button>
<el-button type="text" size="medium" :disabled="!roleDict[scope.row.role]" @click="log(scope.row)">日志</el-button>
<el-button type="text" size="medium" :disabled="!roleDict[scope.row.role]" @click="detail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-pagination
background
:current-page="page"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
<div class="mask"></div>
<Console
ref="batchConsole"
:target_id="target_id"
@refresh="init">
</Console>
<Download
ref="batchDownload"
:target_id="target_id"
@refresh="init">
</Download>
<Upload
ref="batchUpload"
:target_id="target_id"
@refresh="init">
</Upload>
<CollectTraffic
ref="batchCollectTraffic"
:target_id="target_id"
@refresh="init">
</CollectTraffic>
</div>
</template>
<script>
import Header from './module/Header.vue'
import Console from './module/Console.vue'
import Download from './module/Download.vue'
import Upload from './module/Upload.vue'
import CollectTraffic from './module/CollectTraffic'
export default {
name: "RangeConfigManage",
components:{ Header, Console, Download, Upload, CollectTraffic },
data(){
return{
page: 1,
size: 10,
total: 0,
target_id: '',
tableData: [],
selectConfigIds: [],
rangeDict: [],
loading: false,
pendingTimer: null,
roleDict:{
da: '权威目录节点',
client: '客户端节点',
guard: '入口节点',
relay: '路由节点',
exit: '出口节点',
onion: '洋葱服务节点',
}
}
},
mounted() {
},
watch: {
'$store.state.range.targetId': {
handler(newVal, oldVal) {
this.target_id = newVal
this.init()
},
immediate: true
}
},
created() {},
beforeDestroy() {
if (this.pendingTimer) {
clearInterval(this.pendingTimer)
this.pendingTimer = null
}
},
methods:{
init(params={}) {
// this.tableData = getTargetsResponse?.result?.items
// this.total = getTargetsResponse?.result?.total
// const checkedData = JSON.parse(JSON.stringify(this.selectTableData))
const reqParams = {
page: this.page,
size: this.size,
...params
}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.loading = true
this.$axios.get(this.$http.api.getNodeList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.tableData = res?.result?.items
this.total = res?.result?.total
// this.$nextTick(()=>{
// checkedData.forEach((item) => {
// this.$refs?.multipleTable?.toggleRowSelection(this.tableData.find(val => val.id === item.id), true)
// })
// })
let index = this.tableData?.findIndex(item => {
return item.status === 'pending'
})
if (index !== -1) {
if (!this.pendingTimer) {
this.pendingTimer = setInterval(() => {
this.init()
}, 10 * 1000)
}
}
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
query(params) {
this.init(params)
},
// 批量控制台
batchConsole() {
document.querySelector('.mask').style.display = 'block'
this.$refs.batchConsole.visible = true
},
// 批量下载文件
batchDownload() {
this.$refs.batchDownload.getNodeList()
document.querySelector('.mask').style.display = 'block'
this.$refs.batchDownload.visible = true
},
// 批量上传
batchUpload() {
// this.$store.commit('range/setNodeId', '')
this.$refs.batchUpload.getNodeList()
document.querySelector('.mask').style.display = 'block'
this.$refs.batchUpload.visible = true
},
// 批量运行流水线
batchCollectTraffic() {
this.$refs.batchCollectTraffic.getNodeList()
document.querySelector('.mask').style.display = 'block'
this.$refs.batchCollectTraffic.visible = true
},
// 控制台
console(row) {
this.$store.commit('range/setNodeId', row.id)
this.$router.push({
name: 'console',
params: {
closeIcon : true
}
})
},
// 日志
log(row) {
this.$store.commit('range/setNodeId', row.id)
this.$router.push({
name: 'log',
params: {
closeIcon : true
}
})
},
// 详情
detail(row) {
this.$store.commit('range/setNodeId', row.id)
//保存是否正在采集流量
this.$store.commit('node/setStartTraffic', row.collecting)
//保存是否正在结束采集流量
this.$store.commit('node/setEndTraffic', row.analysing)
this.$router.push({ name: 'nodeDetail'})
},
getStatus(status) {
let statusInfo = {
label: '',
class: ''
}
switch (status) {
case 'true':
case 'pending':
statusInfo.label = '正在部署'
statusInfo.class = 'deployNormal'
break;
case 'false':
statusInfo.label = '部署失败'
statusInfo.class = 'deployFail'
break;
case 'complete':
statusInfo.label = '部署完成'
statusInfo.class = 'deploySuccess'
break;
default:
break;
}
return statusInfo
},
// 获取靶场列表字典
getRangeDict() {
const reqParams = {
page: 1,
size: 99,
}
this.$axios.get(this.$http.api.getTargets, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.rangeDict = res?.result?.items.map(item => {
return {
label: item.target_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
// // 处理选中的数据
// handleSelectionChange(selectData) {
// this.selectTableData = selectData
// this.selectConfigIds = selectData.map(item => {
// return item.id
// })
// },
// 修改每页数据条数
handleSizeChange(val) {
console.log(`每页 ${val}`)
this.page=1
this.size=val
this.query()
},
// 修改页数
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.page=val
this.query()
}
}
}
</script>
<style lang='less' scoped="scoped">
.el-pagination {
padding: 15px 55px 0 0 !important;
}
.custom-table {
width: 100%;
height: 100%;
.deploy-status {
display: inline-block;
width: 103px;
height: 28px;
color:#FFFFFF;
border-radius: 3px;
padding: 5px, 12px, 5px, 12px;
background: rgba(227, 249, 233, 0.2);
}
.deployFail {
color: #E9473E;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
.deployNormal {
color: #02DDEA;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
.deploySuccess {
color: #0CCB64;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
}
.range-config-manage {
width: 100%;
height: 100%;
// display: flex;
// flex-direction: column;
// justify-content: flex-start;
.list{
width: 100%;
height: 73%;
// margin-left: 2.5%;
// overflow-y: auto;
// overflow-y: scroll;
// overflow-x: hidden;
// border: none;
}
.list::-webkit-scrollbar {
width: 0px; /* 隐藏滚动条 */
height: 0px;
background-color: transparent; /* 让背景透明 */
}
/* 隐藏火狐浏览器滚动条 */
@-moz-document url-prefix() {
.trackSource {
scrollbar-width: none;
}
}
// 遮罩层
.mask{
position: fixed; /*将元素设置为固定定位*/
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /*通过rgba函数来控制遮罩层的透明度*/
display: none; /*将元素隐藏*/
}
}
</style>

View File

@@ -1,38 +0,0 @@
const getTargetsResponse ={
"code": 200,
"message": "success",
"result": {
"items": [
{
"setting_id": 1,
"traffic_collect_task_id": 1,
"id": 1,
"ip": "1.1.1.1",
"pod_name": "pod_name1",
"nick_name": "第一节点",
"status": "未部署",
"onion": "onion1",
"create_time": "2023-12-26T10:51:15.363Z",
"complete_time": "2023-12-26T10:51:15.363Z"
},
{
"setting_id": 2,
"traffic_collect_task_id": 2,
"id": 2,
"ip": "2.2.2.2",
"pod_name": "pod_name2",
"nick_name": "第二节点",
"status": "已部署",
"onion": "onion2",
"create_time": "2023-12-26T10:51:15.363Z",
"complete_time": "2023-12-26T10:51:15.363Z"
}
],
"total": 0,
"page": 1,
"size": 50,
"pages": 0
}
}
export { getTargetsResponse }

View File

@@ -1,207 +0,0 @@
<template>
<div class="rule-section">
<div class="task-box">
<div class="header">
<span>规则列表</span>
<img @click="addRule" src="../../../../../img/icon/addTrafficBtn.png" alt="" style="width: 194px; height: 40px;">
</div>
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
height="230px"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
>
<el-table-column
align="center"
prop="id"
label="id"
width="100"/>
<el-table-column
align="center"
prop="rule_name"
label="规则名称"
min-width="130"/>
<el-table-column
align="center"
prop="rule"
label="具体规则"
min-width="130"
>
</el-table-column>
<el-table-column
align="center"
prop="update_time"
label="添加时间"
min-width="130"
>
<template slot-scope="scope">
{{ scope.row.update_time | formatDate }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="use_count"
label="使用次数"
min-width="130"/>
<el-table-column
align="center"
label="操作"
min-width="300"
>
<template slot-scope="scope">
<el-button type="text" size="medium" @click="editRule(scope.row)">修改</el-button>
<el-button type="text" size="medium" :loading="scope.row.delLoading" @click="delRule(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="mask"></div>
<RuleForm ref="ruleForm" :is-add="isAdd" @refresh="init"/>
</div>
</template>
<script>
import RuleForm from './module/RuleForm'
export default {
name: 'RuleList',
components: { RuleForm },
props: {},
data() {
return {
tableData: [],
isAdd: true,
visible:false,
loading: false
}
},
created() {
this.init()
},
methods: {
init() {
const reqParams = {
page: 1,
size: 99,
}
this.loading = true
this.$axios.get(this.$http.api.getRuleList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.tableData = res?.result?.items
this.tableData.map(item => {
this.$set(item, 'delLoading', false)
return item
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
// 添加流量采集规则
addRule() {
this.isAdd = true
this.$refs.ruleForm.visible = true
},
// 编辑规则
editRule(row) {
this.isAdd = false
this.$refs.ruleForm.rule_id = row.id
this.$refs.ruleForm.form.rule_name = row.rule_name
this.$refs.ruleForm.form.rule = row.rule
this.$refs.ruleForm.visible = true
},
// 删除规则
delRule(row) {
this.$confirm('此操作将永久删除该规则, 是否继续?', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.del(row)
}).catch(() => {
this.$notify({
title: '已取消删除',
type: 'success',
duration: 2500
})
});
},
del(row) {
const url = this.$http.api.rule + '/' + row.id
row.delLoading = true
this.$axios.delete(url, {}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
row.delLoading = false
})
},
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.resetForm()
}
}
}
</script>
<style lang="less" scoped>
.rule-section{
// height: 100%;
.styleTable {
background-color: transparent !important;
}
.base-input {
width: 100%;
float: left;
margin-top: 2%;
text-align: center;
}
.label-span {
margin-right: 3%;
display: inline-block;
width: 60px;
text-align: right;
}
.but-color {
background-color: #02DDEA !important;
}
.task-box{
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
padding: 2%;
.header{
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
height: 50px;
line-height: 50px;
margin-bottom: 1%;
}
.content {
flex: 1;
::v-deep .el-textarea__inner {
background-color: #1A2648;
}
}
}
}
</style>

View File

@@ -1,391 +0,0 @@
<template>
<div class="task-section">
<div class="task-box">
<div class="header">
<span>任务列表</span>
<img @click="addTask" src="../../../../../img/icon/batchCollectBtn.png" alt="" style="width: 194px; height: 40px;">
</div>
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
v-loading="loading"
height="230px"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@expand-change="handleExpandChange"
>
<el-table-column type="expand">
<template slot-scope="props">
<el-table
class="main-table styleTable"
ref="multipleTable"
v-loading="loading"
height="100%"
style="width: 100%; margin: 5px 20px;"
:data="taskNodeList"
tooltip-effect="dark"
highlight-current-row
>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100"/>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatDate }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatDate }}
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column
align="center"
prop="id"
label="id"
width="60"/>
<el-table-column
align="center"
prop="task_name"
label="任务名称"
min-width="100"/>
<el-table-column
align="center"
prop="traffic_collect_rule_id"
label="采集规则"
min-width="100"/>
<el-table-column
align="center"
prop="collecting"
label="任务状态"
min-width="80"
>
<template slot-scope="scope">
{{ scope.row.collecting ? (scope.row.collecting === true ? '正在采集' : '采集失败') : '采集完成' }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="start_collect_time"
label="开始时间"
min-width="110"
>
<template slot-scope="scope">
{{ scope.row.start_collect_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="end_collect_time"
label="结束时间"
min-width="110"
>
<template slot-scope="scope">
{{ scope.row.end_collect_time | formatTime }}
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
min-width="300"
>
<template slot-scope="scope">
<el-button type="text" size="medium" :loading="startLoading" @click="startTask(scope.row)">开始</el-button>
<el-button type="text" size="medium" :loading="stopLoading" @click="endTask(scope.row)">结束</el-button>
<el-button type="text" size="medium" @click="editTask(scope.row)">修改</el-button>
<el-button type="text" size="medium" @click="delTask(scope.row)">删除</el-button>
<!-- <el-button type="text" size="medium" @click="viewNode(scope.row)">查看节点</el-button> -->
<el-button type="text" size="medium" @click="downloadPack(scope.row)">下载pcap包</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="mask"></div>
<TaskForm
ref="taskForm"
:node-list="nodeList"
:is-add="isAdd"
:target_id="target_id"
@refresh="init">
</TaskForm>
<DownloadPack
ref="downloadPack"
@refresh="init">
</DownloadPack>
</div>
</template>
<script>
import TaskForm from './module/TaskForm'
import DownloadPack from './module/DownloadPack'
export default {
name: 'Console',
components: { TaskForm, DownloadPack },
props: {
target_id: {
typeof: String,
default: 0
},
nodeList: {
typeof: Array,
require: true
},
},
data() {
return {
tableData: [],
taskNodeList: [],
isAdd: true,
visible:false,
loading: false,
startLoading: false,
stopLoading: false
}
},
created() {
this.init()
},
methods: {
init() {
const reqParams = {
page: 1,
size: 99,
}
this.loading = true
this.$axios.get(this.$http.api.getTaskList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
// this.total = res?.result?.total
this.tableData = res?.result?.items
this.tableData.map(item => {
this.$set(item, 'delLoading', false)
return item
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
// 创建批量采集流量任务
addTask() {
this.isAdd = true
document.querySelector('.mask').style.display = 'block'
this.$refs.taskForm.visible = true
},
// 编辑任务
editTask(row) {
this.isAdd = false
this.$refs.taskForm.task_id = row.id ?? ''
this.$refs.taskForm.form.task_name = row.task_name ?? ''
this.$refs.taskForm.form.task_description = row.task_description ?? ''
this.$refs.taskForm.form.node_list = row.node_list ?? []
this.$refs.taskForm.form.traffic_collect_rule_id = row.traffic_collect_rule_id
this.$refs.taskForm.form.filter_noise = row.filter_noise ?? ''
this.$refs.taskForm.visible = true
},
// 开始
startTask(row) {
const reqParams = {
task_id: row.id,
command: 'start'
}
this.startLoading = true
this.$axios.get(this.$http.api.taskStartStop, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '开始统计流量',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
this.startLoading = false
}).finally(() => {
})
},
// 结束
endTask(row) {
const reqParams = {
task_id: row.id,
command: 'stop'
}
this.stopLoading = true
this.$axios.get(this.$http.api.taskStartStop, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.startLoading = false
this.$notify({
title: '结束统计流量',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
setTimeout(() => {
this.stopLoading = false
this.init()
}, 5 * 1000)
})
},
// 删除任务
delTask(row) {
this.$confirm('此操作将永久删除该任务, 是否继续?', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.del(row)
}).catch(() => {
this.$notify({
title: '已取消删除',
type: 'success',
duration: 2500
})
});
},
del(row) {
const url = this.$http.api.trafficTask + '/' + row.id
row.delLoading = true
this.$axios.delete(url, {delete_minio: true}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '删除任务成功',
type: 'success',
duration: 2500
})
this.init()
}
}).catch(err => {
console.log(err)
}).finally(() => {
row.delLoading = false
})
},
// 展开行查看节点
handleExpandChange(row, expandedRows) {
this.getTaskNodeList(row)
},
// // 查看节点
// viewNode(row) {
// this.getTaskNodeList(row)
// },
// 获取节点列表
getTaskNodeList(row) {
const params = {
page:1,
size: 99,
task_id: row.id
}
this.$axios.get(this.$http.api.getTrafficNodes, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.taskNodeList = res?.result?.items
}
}).catch(err => {
console.log(err)
})
},
// 下载pcap包
downloadPack(row) {
this.$refs.downloadPack.task_id = row.id
this.$refs.downloadPack.init()
document.querySelector('.mask').style.display = 'block'
this.$refs.downloadPack.visible = true
}
}
}
</script>
<style lang="less" scoped>
.task-section{
// height: 100%;
.styleTable {
background-color: transparent !important;
}
.base-input {
width: 100%;
float: left;
margin-top: 2%;
text-align: center;
}
.label-span {
margin-right: 3%;
display: inline-block;
width: 60px;
text-align: right;
}
.but-color {
background-color: #02DDEA !important;
}
.task-box{
width: 100%;
min-height: 45%;
display: flex;
flex-direction: column;
padding: 2%;
.header{
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
height: 50px;
line-height: 50px;
margin-bottom: 1%;
}
.content {
flex: 1;
::v-deep .el-textarea__inner {
background-color: #1A2648;
}
}
}
::v-deep .el-table tbody tr>td {
// background: rgba(25, 33, 61, 0.5) !important;
background-color: #0A162C !important;
}
}
</style>

View File

@@ -1,110 +0,0 @@
<template>
<div class="console-dialog" v-if="visible">
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" style="color:#f8fdff">批量采集流量</el-tag>
</div>
<TaskList style="height: 42%;" :node-list="nodeList"></TaskList>
<RuleList style="height: 42%;"></RuleList>
<footer class="anDiv">
<el-button class="glBut but-color" type="primary" @click="close">确定</el-button>
</footer>
</div>
</template>
<script>
import TaskList from './TaskList'
import RuleList from './RuleList'
export default {
name: 'Console',
components: { TaskList, RuleList },
props: {
target_id: {
typeof: String,
required: true,
default: 0
}
},
data() {
return {
visible:false,
nodeList: []
}
},
methods: {
getNodeList() {
const reqParams = {
page: 1,
size: 99
}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.$axios.get(this.$http.api.getNodeList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.nodeList = res?.result?.items
}
}).catch(err => {
console.log(err)
}).finally(() => {})
},
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.$emit('refresh')
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.console-dialog{
z-index: 997;
width: 1331px;
height: 800px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../../img/background/NodeListDialog.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 8%;
padding-top: 1.8%
}
.tag{
text-align: left;
margin-left: 2%;
.tags{
margin-top: 1%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.but-color {
background-color: #02DDEA !important;
}
.anDiv{
width: 100%;
float: left;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
</style>

View File

@@ -1,223 +0,0 @@
<template>
<div
class="custom-dialog"
v-if="visible"
>
<span class="dialog-title">pcap包下载</span>
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" style="float: right; padding-right: 10%;padding-top: 2.5%" @click="close"></i>
<div class="basic-box">
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
v-loading="loading"
height="420px"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionChange"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="bucket_name"
label="桶名称"
width="80"/>
<el-table-column
align="center"
prop="file_name"
label="文件名称"
min-width="100"/>
<el-table-column
align="center"
prop="size"
label="文件大小"
min-width="100"
>
</el-table-column>
<el-table-column
align="center"
prop="last_modified"
label="生成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.last_modified | formatTime }}
</template>
</el-table-column>
</el-table>
</div>
<div class="anDiv">
<div>
<el-button class="glBut but-color" type="primary" @click="download" :loading="downLoading">下载</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RuleForm',
// props: ['task_id'],
data() {
return {
visible: false,
loading: false,
downLoading: false,
task_id: '',
tableData: [],
selectFileNameList: []
}
},
created() {
},
methods: {
init() {
const reqParams = {
page: 1,
size: 99,
task_id: this.task_id
}
this.loading = true
this.$axios.get(this.$http.api.getPackList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.tableData = res?.result?.items
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
handleSelectionChange(selectData) {
this.selectFileNameList = selectData.map(item => {
return {
bucket_name: item.bucket_name,
file_name: item.file_name
}
})
},
close() {
this.resetForm()
document.querySelector('.mask').style.display = 'none'
this.visible = false
},
resetForm() {
},
// 下载
download() {
if (this.selectFileNameList.length <= 0) {
this.$notify({
title: '请勾选要下载的文件',
type: 'warning',
duration: 2500
})
return
}
const reqParams = {
task_id: this.task_id,
object_info_list: JSON.stringify(this.selectFileNameList)
}
this.downLoading = true
this.$axios.getFile(this.$http.api.downloadPacp, reqParams).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载文件成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载文件失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.downLoading = false
this.close()
})
}
}
}
</script>
<style lang="less" scoped>
.custom-dialog{
z-index: 998;
width: 620px;
height: 555px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../../../img/background/dialog660-553.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
// background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.dialog-title {
font-size: 20px;
float: left;
margin: 11px 0 11px 20px;
}
.basic-box {
width: 100%;
min-height: 100%;
padding: 2%;
display: flex;
flex-direction: column;
.content {
flex: 1;
.bcmcDiv{
width: 100%;
float: left;
margin-top: 5%;
text-align: center;
.bcmc {
width: 60%;
}
}
}
}
.anDiv{
width: 100%;
float: left;
margin-bottom: 10%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
.but-color {
background-color: #02DDEA;
}
}
}
</style>

View File

@@ -1,164 +0,0 @@
<template>
<div
class="rule-dialog"
v-if="visible"
>
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" style="float: right; padding-right: 8%;padding-top: 3%" @click="close"></i>
<el-form
ref="ruleForm"
:model="form"
:rules="rules"
label-width="150px"
class="rule-form"
>
<el-row>
<el-col :span="20">
<el-form-item label="规则名称" prop="rule_name">
<el-input v-model="form.rule_name" placeholder="请输入规则名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-form-item label="规则" prop="rule">
<el-input type="textarea" v-model="form.rule" placeholder="请输入规则"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="submit-footer">
<div>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="loading">确定</el-button>
</div>
</div>
<div class="mask"></div>
</div>
</template>
<script>
export default {
name: 'RuleForm',
props: ['isAdd'],
data() {
return {
visible: false,
loading: false,
form: {
rule_name: '', // 规则名称
rule: '' // 规则
},
rule_id: '',
rules: {
rule_name: [
{ required: true, message: '请输入规则名称', trigger: 'blur' }
],
rule: [
{ required: true, message: '请输入规则', trigger: 'blur' }
]
}
}
},
methods: {
close() {
this.resetForm()
// document.querySelector('.mask').style.display = 'none'
this.visible = false
},
submit() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
if (this.isAdd) {
this.add()
} else {
this.edit()
}
}
})
},
add () {
this.loading = true
const url = this.$http.api.rule
this.$axios.post(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '创建规则成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
edit() {
this.loading = true
const url = this.$http.api.rule + `/${this.rule_id}`
this.$axios.put(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '编辑规则成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
resetForm() {
this.form = {
rule_name: '', // 规则名称
rule: '' // 规则
}
}
}
}
</script>
<style lang="less" scoped>
.rule-dialog{
width: 620px;
height: 555px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../../../img/background/addRuleBg.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: 100% 100%; /* 宽度为100%,高度自适应保持宽高比 */
.rule-form {
margin-top: 70px;
text-align: left;
}
.submit-footer{
width: 100%;
float: left;
margin-top: 200px;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 2%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
.but-color {
background-color: #02DDEA;
}
}
}
</style>

View File

@@ -1,522 +0,0 @@
<template>
<div class="console-dialog" v-if="visible">
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='添加任务') ? '#f8fdff': '#565e6e'}">添加任务</el-tag>
<el-tag class="tags" :style="{'color': (tag==='被操作节点') ? '#f8fdff': '#565e6e'}">被操作节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='操作') ? '#f8fdff': '#565e6e'}">操作</el-tag>
</div>
<div class="basic-box" v-if="tag==='添加任务'">
<el-form
ref="addTaskForm"
:model="form"
:rules="taskRules"
label-width="150px"
class="basic-form"
size="small"
>
<el-row>
<el-col :span="12" :offset="6">
<el-form-item label="任务名称" prop="task_name">
<el-input v-model="form.task_name" placeholder="请输入任务名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" :offset="6">
<el-form-item label="任务描述" prop="task_description">
<el-input v-model="form.task_description" placeholder="请输入任务描述">
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<footer class="anDiv">
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点', true)">下一步</el-button>
</footer>
</div>
<div class="table-box" v-if="tag==='被操作节点'">
<div class="tip-message">请勾选多个节点</div>
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
height="100%"
style="width: 100%;"
:data="nodeList"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionNode"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<template slot-scope="scope">
<div class="deploy-status">
<svg-icon :icon-class="getStatus(scope.row.status).class"></svg-icon>
<span :class="getStatus(scope.row.status).class">{{ getStatus(scope.row.status).label }}</span>
</div>
</template>
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatDate }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatDate }}
</template>
</el-table-column>
</el-table>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('添加任务')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('操作', true)">下一步</el-button>
</div>
</div>
</div>
<div class="basic-box" v-if="tag==='操作'">
<el-form
ref="operateForm"
:model="form"
:rules="oprateRules"
label-width="150px"
class="basic-form"
size="small"
>
<el-row>
<el-col :span="12" :offset="6">
<el-form-item label="选择规则" prop="traffic_collect_rule_id">
<el-select v-model="form.traffic_collect_rule_id" placeholder="请选择规则" @focus="getRuleList">
<el-option
v-for="item in ruleDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" :offset="6">
<el-form-item label="是否过滤噪音流量" prop="filter_noise">
<el-radio-group v-model="form.filter_noise">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="loading">确认</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TaskForm',
props: {
isAdd: {
typeof: Boolean,
required: true
},
nodeList: {
typeof: Array,
require: true
},
target_id: {
typeof: String,
required: true
}
},
data() {
return {
visible:false,
loading: false,
tag:"添加任务",
task_id: '',
ruleDict: [],
form: {
task_name: '', // 任务名称
task_description: '', // 任务描述
node_list: [], // 节点列表
traffic_collect_rule_id: null, // 选择规则
filter_noise: true, // 是否过滤噪音流量
},
taskRules: {
task_name:[
{ required: true, message: '请输入任务名称', trigger: 'blur' }
],
task_description: [
{ required: true, message: '请输入任务描述', trigger: 'blur' }
]
},
oprateRules: {
traffic_collect_rule_id: [
{ required: true, message: '请选择规则', trigger: 'blur' }
],
filter_noise: [
{ required: true, message: '请选择是否过滤噪音流量', trigger: 'blur' }
]
},
setting_id: '',
torDict: [],
networkDict: [],
imageDict: []
}
},
created() {
// 获取规则字典
this.getRuleList()
},
methods: {
// 选择添加任务
handleSelectionNode(selectRows) {
this.form.node_list = selectRows.map(row => {
return row.id
})
},
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.tag = '添加任务'
this.resetForm()
},
// 重置
resetForm() {
this.form = {
task_name: '', // 任务名称
task_description: '', // 任务描述
node_list: [], // 节点列表
traffic_collect_rule_id: null, // 选择规则
filter_noise: true, // 是否过滤噪音流量
}
},
// 选择哪个页面
updateTag(val, isValid){
if (val === '被操作节点' && isValid) {
this.$refs.addTaskForm.validate((valid) => {
if (valid) {
this.tag=val
}
})
} else if(val === '操作' && isValid) {
if (this.form.node_list.length > 0) {
this.tag=val
} else {
this.$notify({
title: '请勾选要被操作的节点!',
type: 'success',
duration: 2500
})
return
}
} else {
this.tag=val
}
},
// 获取规则列表
getRuleList() {
const reqParams = {
page: 1,
size: 99,
}
this.loading = true
this.$axios.get(this.$http.api.getRuleList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.ruleDict = res?.result?.items.map(item => {
return {
label: item.rule_name,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
// 提交数据
submit() {
this.$refs.operateForm.validate((valid) => {
if (valid) {
if (this.isAdd) {
this.add()
} else {
this.edit()
}
}
})
},
add () {
this.loading = true
const url = this.$http.api.trafficTask
this.$axios.post(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '创建任务成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
edit() {
this.loading = true
const url = this.$http.api.trafficTask + `/${this.task_id}`
this.$axios.put(url, this.form).then(res => {
if (res.code == 200 || res.code == "OK") {
this.resetForm()
this.close()
this.$emit('refresh')
this.$notify({
title: '编辑任务成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
getStatus(status) {
let statusInfo = {
label: '',
class: ''
}
switch (status) {
case 'true':
case 'pending':
statusInfo.label = '正在部署'
statusInfo.class = 'deployNormal'
break;
case 'false':
statusInfo.label = '部署失败'
statusInfo.class = 'deployFail'
break;
case 'complete':
statusInfo.label = '部署完成'
statusInfo.class = 'deploySuccess'
break;
default:
break;
}
return statusInfo
}
}
}
</script>
<style lang="less" scoped>
.console-dialog{
z-index: 997;
width: 1171px;
height: 705px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../../../img/background/NodeListDialog.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 7%;
padding-top: 1.8%
}
.tag{
margin-left: 9%;
.tags{
margin-right: 5%;
margin-top: 1%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.basic-form {
margin-top: 50px;
text-align: left;
::v-deep .el-select {
width: 100%;
}
}
.but-color {
background-color: #02DDEA !important;
}
.basic-box{
.anDiv{
width: 100%;
float: left;
margin-top: 450px;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
.table-box{
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
padding: 2%;
.tip-message{
width: 100%;
height: 50px;
text-align: left;
padding: 11px 0 12px 20px;
font-size: 20px;
background-color: #1A2648;
border-radius: 4px;
margin-bottom: 2%;
}
.content {
flex: 1;
display: flex;
flex-direction: row;
.deploy-status {
display: inline-block;
width: 103px;
height: 28px;
color:#FFFFFF;
border-radius: 3px;
padding: 5px, 12px, 5px, 12px;
background: rgba(227, 249, 233, 0.2);
}
.deployFail {
color: #E9473E;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
.deployNormal {
color: #02DDEA;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
.deploySuccess {
color: #0CCB64;
width: 56px;
height: 28px;
margin-left: 10px;
font-family: PingFang SC;
font-size: 14px;
font-weight: 400;
line-height: 28px;
letter-spacing: 0em;
text-align: left;
}
}
.anDiv{
width: 100%;
float: left;
margin-bottom: 7%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
}
</style>

View File

@@ -1,521 +0,0 @@
<template>
<div class="console-dialog" v-if="visible">
<!-- 在此处指定弹窗的样式和内容 -->
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='主节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('主节点')">主节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='被操作节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('被操作节点')">被操作节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='控制台') ? '#f8fdff': '#565e6e'}" @click="updateTag('控制台')">控制台</el-tag>
</div>
<div class="basic-box" v-if="tag==='主节点'">
<div class="tip-message">请选择主节点作为打开文件系统的依据</div>
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionMain"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<!-- <template slot-scope="scope">
{{ scope.row.has_deployed ? '已部署' : '未部署' }}
</template> -->
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatDate }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatDate }}
</template>
</el-table-column>
</el-table>
</div>
<footer class="anDiv">
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">下一步</el-button>
</footer>
</div>
<div class="basic-box" v-if="tag==='被操作节点'">
<div class="tip-message">请勾选多个节点</div>
<div class="content">
<el-table
class="main-table styleTable"
ref="multipleTable"
height="100%"
style="width: 100%;"
:data="tableData"
tooltip-effect="dark"
highlight-current-row
@selection-change="handleSelectionMain"
>
<el-table-column
align="center"
type="selection"
width="80"/>
<el-table-column
align="center"
prop="id"
label="id"
width="80"/>
<el-table-column
align="center"
prop="nick_name"
label="节点昵称"
min-width="100"/>
<el-table-column
align="center"
prop="role"
label="角色"
min-width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.role"></svg-icon>
</template>
</el-table-column>
<el-table-column
align="center"
prop="ip"
label="ip"
min-width="150"/>
<el-table-column
align="center"
prop="status"
label="部署状态"
min-width="100"
>
<!-- <template slot-scope="scope">
{{ scope.row.has_deployed ? '已部署' : '未部署' }}
</template> -->
</el-table-column>
<el-table-column
align="center"
prop="create_time"
label="创建开始时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.create_time | formatDate }}
</template>
</el-table-column>
<el-table-column
align="center"
prop="complete_time"
label="创建完成时间"
min-width="100"
>
<template slot-scope="scope">
{{ scope.row.complete_time | formatDate }}
</template>
</el-table-column>
</el-table>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('主节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('控制台')">下一步</el-button>
</div>
</div>
</div>
<div class="basic-box" v-if="tag==='控制台'">
<div class="tip-message">主节点名称10-relaytwlgthavry
已勾选的节点名称列表11-relayyqbgcxuewy12-relayztitnmkgfl</div>
<div class="content">
<el-input
type="textarea"
:rows="25"
placeholder="请输入命令"
v-model="textarea">
</el-input>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="submit" :loading="loading">确认</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Console',
props: {
tableData: {
typeof: Array,
require: true
},
target_id: {
typeof: String,
required: true
}
},
data() {
return {
visible:false,
loading: false,
tag:"主节点",
setting_id: '',
form: {
role: '', // 角色选择
tor_version: '', // tor版本
image_id: '', // 选择镜像
replicas: '', // 复本数
bandwidth: '', // 宽带限制
memory: '', // 内存限制
service: '', // 服务器地址
out_port: '', // 代理端口
or_port: '', // or端口
dir_port: '', // dir端口
socks_port: '', // 客户端监听端口
control_port: '', // 客户端控制端口
country_id: '', // 国家
network_id: '', // 网络
deployType: '', // 部署方式
direct: false, // 直接部署true 仅添加配置false
start_tcpdump: false // 启动采集程序并部署 true
},
roleDict:[
{label: '权威目录节点', value: 'da'},
{label: '路由节点', value: 'relay'},
{label: '出口节点', value: 'exit'},
{label: '洋葱服务节点', value: 'onion'},
{label: '客户端节点', value: 'client'},
{label: '入口节点', value: 'guard'},
{label: '其他节点', value: 'other'}
],
torDict: [],
networkDict: [],
imageDict: []
}
},
methods: {
// 选择主节点
handleSelectionMain() {},
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.tag = '主节点'
this.resetForm()
},
// 重置
resetForm() {
this.form = {
role: '', // 角色选择
tor_version: '', // tor版本
image_id: '', // 选择镜像
replicas: '', // 复本数
bandwidth: '', // 宽带限制
memory: '', // 内存限制
service: '', // 服务器地址
out_port: '', // 代理端口
or_port: '', // or端口
dir_port: '', // dir端口
socks_port: '', // 客户端监听端口
control_port: '', // 客户端控制端口
country_id: '', // 国家
network_id: '', // 网络
deployType: '', // 部署方式
direct: false, // 直接部署true 仅添加配置false
start_tcpdump: false // 启动采集程序并部署 true
}
},
// 选择哪个页面
updateTag(val){
this.tag=val
},
// 角色选择
selectRole(node) {
if (node === 'other') {
this.$emit('openRoleForm')
}
// 获取torDict
this.$axios.get(this.$http.api.getTorDict, {role: this.form.role}).then(res => {
if (res.code == 200 || res.code == "OK") {
this.torDict = res?.result
}
}).catch(err => {
console.log(err)
})
// 获取镜像字典
this.getImageDict()
},
// tor选择
selectTor(tor){
// 获取镜像字典
this.getImageDict()
},
// 获取imageDict
getImageDict(){
const params = {
page:1,
size: 99,
image_name: this.form.role,
image_version: this.form.tor_version
}
this.$axios.get(this.$http.api.getImageDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.imageDict = res?.result?.items.map(item => {
return {
label: item.image_name + '-' + item.image_version,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
// 获取网络
getNetworkDict(){
const params = {
page:1,
size: 99,
country_id: this.form.country_id,
}
this.$axios.get(this.$http.api.getNetworkDict, params).then(res => {
if (res.code == 200 || res.code == "OK") {
this.networkDict = res?.result?.items.map(item => {
return {
label: item.cidr,
value: item.id
}
})
}
}).catch(err => {
console.log(err)
})
},
selectDeploy(deployType) {
switch (deployType) {
case 'direct':
this.form.direct = true
break
case 'only':
this.form.direct = false
break
case 'start':
this.form.start_tcpdump = true
break
default:
this.form.direct = true
break
}
},
// 提交数据
submit() {
// const submitForm = this.setSubmitForm()
// if (this.isAdd) {
// this.add(submitForm)
// } else {
// this.edit(submitForm)
// }
},
setSubmitForm() {
const {role, image_id, replicas, bandwidth, memory } = this.form
let submitForm = {role, image_id, replicas, bandwidth, memory }
if (this.form.role === 'other') {
submitForm.role = this.otherRole
} else if (this.form.role==='da'||this.form.role==='relay'||this.form.role ==='guard'||this.form.role ==='exit') {
submitForm.tor_version = this.form.tor_version
submitForm.or_port = this.form.or_port
submitForm.dir_port = this.form.dir_port
} else if (this.form.role==='onion') {
submitForm.tor_version = this.form.tor_version
submitForm.service = this.form.service
submitForm.socks_port = this.form.socks_port
submitForm.control_port = this.form.control_port
} else if (this.form.role==='client') {
submitForm.tor_version = this.form.tor_version
submitForm.out_port = this.form.out_port
submitForm.socks_port = this.form.socks_port
submitForm.control_port = this.form.control_port
}
return submitForm
},
add(submitForm) {
this.loading = true
const url = this.$http.api.setting + `/?target_id=${this.target_id}&network_id=${this.form.network_id}&direct=${this.form.direct}&start_tcpdump=${this.form.start_tcpdump}`
this.$axios.post(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
this.$notify({
title: '添加靶场配置成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
},
edit(submitForm) {
this.loading = true
const url = this.$http.api.setting + `/?target_id=${this.target_id}&setting_id=${this.setting_id}&network_id=${this.form.network_id}&direct=${this.form.direct}&start_tcpdump=${this.form.start_tcpdump}`
this.$axios.put(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.close()
this.$emit('refresh')
this.$notify({
title: '添加靶场配置成功',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang="less" scoped>
.console-dialog{
z-index: 997;
width: 1331px;
height: 800px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../img/background/NodeListDialog.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 8%;
padding-top: 1.8%
}
.tag{
margin-left: 15%;
.tags{
margin-right: 5%;
margin-top: 1%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.base-input {
width: 100%;
float: left;
margin-top: 2%;
text-align: center;
}
.label-span {
margin-right: 3%;
display: inline-block;
width: 60px;
text-align: right;
}
.but-color {
background-color: #02DDEA !important;
}
.basic-box{
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
padding: 2%;
.tip-message{
width: 100%;
height: 50px;
line-height: 50px;
font-size: 20px;
background-color: #1A2648;
border-radius: 4px;
margin-bottom: 2%;
}
.content {
flex: 1;
::v-deep .el-textarea__inner {
background-color: #1A2648;
}
}
.anDiv{
width: 100%;
float: left;
margin-bottom: 7%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
}
</style>

View File

@@ -1,274 +0,0 @@
<template>
<div class="console-dialog" v-if="visible">
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='主节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('主节点')">主节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='被操作节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('被操作节点')">被操作节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='文件管理') ? '#f8fdff': '#565e6e'}" @click="updateTag('文件管理')">文件管理</el-tag>
</div>
<div class="basic-box" v-show="tag==='主节点'">
<div class="tip-message">请选择主节点作为打开文件系统的依据</div>
<div class="content">
<!-- 主节点 -->
<MainNode
:node-list="nodeList"
@selectMainNode="selectMainNode"
></MainNode>
</div>
<footer class="anDiv">
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">下一步</el-button>
</footer>
</div>
<div class="basic-box" v-show="tag==='被操作节点'">
<div class="tip-message">请勾选多个节点</div>
<div class="content">
<!-- 被操作节点 -->
<OperateNode
:node-list="nodeList"
@selectOperateNode="selectOperateNode"
></OperateNode>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('主节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('文件管理')">下一步</el-button>
</div>
</div>
</div>
<div class="basic-box" v-if="tag==='文件管理'">
<div class="tip-message" style="height: 76px;">
<span>{{`主节点名称:${master_node_name}`}}<br/>{{`已勾选的节点名称列表:${batch_node_name_list.join('、')}`}}</span>
</div>
<!-- 文件管理 -->
<FileManager
ref="fileManager"
></FileManager>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="download" :loading="loading">下载</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import MainNode from '../components/MainNode.vue'
import OperateNode from '../components/OperateNode.vue'
import FileManager from '../components/DownloadFileManager.vue'
import qs from 'qs'
export default {
name: 'Download',
components:{
MainNode,
OperateNode,
FileManager
},
props: {
target_id: {
typeof: String,
required: true
}
},
data() {
return {
nodeList: [],
downloadForm: {
master_node_id: '',
batch_node_id_list: [],
current_dir: '',
filename: ''
},
master_node_name: '',
batch_node_name_list: [],
visible:false,
loading: false,
tag:"主节点",
form: {}
}
},
methods: {
// 获取节点列表
getNodeList() {
const reqParams = {
page: 1,
size: 99
}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.$axios.get(this.$http.api.getNodeList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.nodeList = res?.result?.items
}
}).catch(err => {
console.log(err)
}).finally(() => {})
},
// 选择主节点
selectMainNode({ id, nick_name }) {
this.$store.commit('range/setNodeId', id)
this.downloadForm.master_node_id = id
this.master_node_name = nick_name
},
// 选择被操作节点
selectOperateNode(selectRows) {
this.batch_node_name_list = []
this.downloadForm.batch_node_id_list = selectRows.map(row => {
this.batch_node_name_list.push(row.nick_name)
return row.id
})
},
// 选择上传文件
selectUploadFile({path, file}) {
this.downloadForm.current_dir = path
this.downloadForm.file = file
},
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.tag = '主节点'
this.resetForm()
},
// 重置
resetForm() {
this.form = {}
},
// 选择哪个页面
updateTag(val){
this.tag=val
},
// 提交数据
download() {
this.downloadForm.current_dir = this.$refs.fileManager.selectNode.path
this.downloadForm.filename = this.$refs.fileManager.filename
const reqParams = qs.stringify(this.downloadForm, { arrayFormat: 'repeat' })
const url = this.$http.api.batchDownload + '?' + reqParams
this.$axios.getFile(url).then(res => {
if (res.status == 200 || res.statusText == "OK") {
const { data, headers } = res
const fileName = headers['content-disposition'].replace(/\w+;filename=(.*)/, '$1')
// 此处当返回json文件时需要先对data进行JSON.stringify处理其他类型文件不用做处理
//const blob = new Blob([JSON.stringify(data)], ...)
const blob = new Blob([data], {type: headers['content-type']})
let dom = document.createElement('a')
let url = window.URL.createObjectURL(blob)
dom.href = url
dom.download = decodeURI(fileName)
dom.style.display = 'none'
document.body.appendChild(dom)
dom.click()
dom.parentNode.removeChild(dom)
window.URL.revokeObjectURL(url)
this.$notify({
title: '下载文件成功',
type: 'success',
duration: 2500
})
} else {
this.$notify({
title: '下载文件失败',
type: 'success',
duration: 2500
})
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.close()
})
}
}
}
</script>
<style lang="less" scoped>
.console-dialog{
z-index: 997;
width: 1331px;
height: 800px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../img/background/NodeListDialog.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 8%;
padding-top: 1.8%
}
.tag{
margin-left: 15%;
.tags{
margin-right: 5%;
margin-top: 1%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.base-input {
width: 100%;
float: left;
margin-top: 2%;
text-align: center;
}
.label-span {
margin-right: 3%;
display: inline-block;
width: 60px;
text-align: right;
}
.but-color {
background-color: #02DDEA !important;
}
.basic-box{
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
padding: 2%;
.tip-message{
width: 100%;
height: 50px;
text-align: left;
padding: 11px 0 12px 20px;
font-size: 20px;
background-color: #1A2648;
border-radius: 4px;
margin-bottom: 2%;
}
.content {
flex: 1;
display: flex;
flex-direction: row;
}
.anDiv{
width: 100%;
float: left;
margin-bottom: 7%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
}
</style>

View File

@@ -1,146 +0,0 @@
<template>
<div class="head">
<div class="role-select">
<el-select v-model="role" clearable placeholder="节点角色" @change="query">
<el-option
v-for="item in roleDict"
:key="item.value"
:label="item.label"
:value="item.value">
<svg-icon :icon-class="item.value ? item.value : 'other'"></svg-icon>
<span style="margin-left: 10px;">{{ item.label }}</span>
</el-option>
</el-select>
</div>
<div class="deploy-select">
<el-select v-model="status" clearable placeholder="节点部署状态" @change="query">
<el-option
v-for="item in statusDict"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<el-button v-if="false" type="primary" @click="batchConsole">批量控制台</el-button>
<el-button v-if="true" type="primary" @click="batchDownload">批量下载文件</el-button>
<el-button v-if="true" type="primary" @click="batchUpload">批量上传文件</el-button>
<el-button v-if="true" type="primary" @click="batchCollectTraffic">批量采集流量</el-button>
</div>
</template>
<script>
export default {
name: 'Header',
data() {
return {
role: '',
status: '',
roleDict:[
{label: '全部节点', value: ''},
{label: '权威目录节点', value: 'da'},
{label: '路由节点', value: 'relay'},
{label: '出口节点', value: 'exit'},
{label: '洋葱服务节点', value: 'onion'},
{label: '客户端节点', value: 'client'},
{label: '入口节点', value: 'guard'}
],
statusDict: [
{value: 'pending', label: '正在部署'},
{value: 'false', label: '部署失败'},
{value: 'complete',label: '部署完成'}
]
}
},
methods: {
// 查询
query() {
let params = {}
if (this.role !== '') {
params.role = this.role
}
if (this.status !== '') {
params.status = this.status
}
this.$emit('query', params)
},
// 批量控制台
batchConsole() {
this.$emit('batchConsole')
},
// 批量下载文件
batchDownload() {
this.$emit('batchDownload')
},
// 批量上传文件
batchUpload() {
this.$emit('batchUpload')
},
// 批量采集流量
batchCollectTraffic() {
this.$emit('batchCollectTraffic')
}
}
}
</script>
<style lang="less" scoped>
.head{
width: 100%;
height: 15%;
margin-top: 1%;
text-align: right;
/*background-color: #5daf34;*/
.block{
display: inline-block;
margin-left: 2%;
}
.input{
display: inline-block;
height: 60%;
width: 10%;
margin-left: 0.5%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
.deploy-select{
display: inline-block;
height: 60%;
width: 10%;
margin-top: 0.5%;
margin-left: 0.5%;
margin-right: 1%;
.el-input::placeholder {
width: auto;
}
.icon-group {
display: flex; /* 设置容器为 Flexbox 容器 */
align-items: center; /* 垂直居中图片 */
gap: 5px; /* 图片和文字之间的间距,可以根据需要进行调整 */
}
.icon-group img {
transform: scale(1);
margin-right: 15px;
margin-top: 6px;
}
}
::v-deep .role-select{
display: inline-block;
}
}
</style>

View File

@@ -1,285 +0,0 @@
<template>
<div class="console-dialog" v-if="visible">
<i class="el-icon-close" @click="close"></i>
<div class="tag">
<el-tag class="tags" :style="{'color': (tag==='主节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('主节点')">主节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='被操作节点') ? '#f8fdff': '#565e6e'}" @click="updateTag('被操作节点')">被操作节点</el-tag>
<el-tag class="tags" :style="{'color': (tag==='文件管理') ? '#f8fdff': '#565e6e'}" @click="updateTag('文件管理')">文件管理</el-tag>
</div>
<div class="basic-box" v-show="tag==='主节点'">
<div class="tip-message">请选择主节点作为打开文件系统的依据</div>
<div class="content">
<!-- 主节点 -->
<MainNode
:node-list="nodeList"
@selectMainNode="selectMainNode"
></MainNode>
</div>
<footer class="anDiv">
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">下一步</el-button>
</footer>
</div>
<div class="basic-box" v-show="tag==='被操作节点'">
<div class="tip-message">请勾选多个节点</div>
<div class="content">
<!-- 被操作节点 -->
<OperateNode
:node-list="nodeList"
@selectOperateNode="selectOperateNode"
></OperateNode>
</div>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('主节点')">上一步</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('文件管理')">下一步</el-button>
</div>
</div>
</div>
<div class="basic-box" v-if="tag==='文件管理'">
<div class="tip-message" style="height: 76px;">
<span>{{`主节点名称:${master_node_name}`}}<br/>{{`已勾选的节点名称列表:${batch_node_name_list.join('、')}`}}</span>
</div>
<!-- 文件管理 -->
<FileManager
ref="fileManager"
></FileManager>
<div class="anDiv">
<div>
<el-button class="glBut" type="primary" @click="close">取消</el-button>
<el-button class="glBut but-color" type="primary" @click="updateTag('被操作节点')">上一步</el-button>
<label
class="file-upload-button"
for="file-input-upload"
>
<span>上传</span>
<input
type="file"
id="file-input-upload"
@change="submit"
style="display: none;"
/>
</label>
</div>
</div>
</div>
</div>
</template>
<script>
import MainNode from '../components/MainNode.vue'
import OperateNode from '../components/OperateNode.vue'
import FileManager from '../components/UploadFileManager.vue'
import qs from 'qs'
export default {
name: 'Upload',
components:{
MainNode,
OperateNode,
FileManager
},
props: {
target_id: {
typeof: String,
required: true
}
},
data() {
return {
nodeList: [],
uploadForm: {
master_node_id: '',
batch_node_id_list: [],
current_dir: ''
},
file: '',
master_node_name: '',
batch_node_name_list: [],
visible:false,
loading: false,
tag:"主节点",
form: {}
}
},
methods: {
// 获取节点列表
getNodeList() {
const reqParams = {
page: 1,
size: 99
}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.$axios.get(this.$http.api.getNodeList, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.nodeList = res?.result?.items
}
}).catch(err => {
console.log(err)
}).finally(() => {})
},
// 选择主节点
selectMainNode({ id, nick_name }) {
this.$store.commit('range/setNodeId', id)
this.uploadForm.master_node_id = id
this.master_node_name = nick_name
},
// 选择被操作节点
selectOperateNode(selectRows) {
this.batch_node_name_list = []
this.uploadForm.batch_node_id_list = selectRows.map(row => {
this.batch_node_name_list.push(row.nick_name)
return row.id
})
},
// // 选择上传文件
// selectUploadFile({path}) {
// this.uploadForm.current_dir = path
// // this.file = file
// },
// 关闭
close() {
document.querySelector('.mask').style.display = 'none'
this.visible = false
this.tag = '主节点'
this.resetForm()
},
// 重置
resetForm() {
this.form = {}
},
// 选择哪个页面
updateTag(val){
this.tag=val
},
// 提交数据
submit(e) {
this.uploadForm.current_dir = this.$refs.fileManager.selectNode.path
this.file = e.target.files[0]
const params = qs.stringify(this.uploadForm, { arrayFormat: 'repeat' })
const url = this.$http.api.batchUpload + '?' + params
const submitForm = new FormData()
submitForm.append('file', this.file)
this.loading = true
this.$axios.postFormData(url, submitForm).then(res => {
if (res.code == 200 || res.code == "OK") {
this.$notify({
title: '批量上传成功',
type: 'success',
duration: 2500
})
// 更新当前路径下的文件信息
this.$refs.fileManager.updateCurrentPath()
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
})
}
}
}
</script>
<style lang="less" scoped>
.console-dialog{
z-index: 997;
width: 1331px;
height: 800px;
position: absolute; /* 绝对定位 */
top: 50%; /* 向下偏移50% */
left: 50%; /* 向右偏移50% */
transform: translate(-50%, -50%); /* 回移50% */
background-image:url('../../../../img/background/NodeListDialog.svg');
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
.el-icon-close{
float: right;
padding-right: 8%;
padding-top: 1.8%
}
.tag{
margin-left: 15%;
.tags{
margin-right: 5%;
margin-top: 1%;
font-size: 23px;
border: none;
background-color: transparent !important;
color: #565e6e;
}
}
.base-input {
width: 100%;
float: left;
margin-top: 2%;
text-align: center;
}
.label-span {
margin-right: 3%;
display: inline-block;
width: 60px;
text-align: right;
}
.but-color {
background-color: #02DDEA !important;
}
.file-upload-button {
display: inline-block;
padding: 5px 33px;
background-color: #02DDEA;
color: #1b7cc4;
border: none;
border-radius: 0;
cursor: pointer;
font-size: 14px;
}
.file-upload-button:hover {
background-color: #02DDEA;
}
.basic-box{
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
padding: 2%;
.tip-message{
width: 100%;
height: 50px;
text-align: left;
padding: 11px 0 12px 20px;
font-size: 20px;
background-color: #1A2648;
border-radius: 4px;
margin-bottom: 2%;
}
.content {
flex: 1;
display: flex;
flex-direction: row;
}
.anDiv{
width: 100%;
float: left;
margin-bottom: 7%;
text-align: center;
.glBut{
width: 90px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 5%;
margin-right: 5%;
background-color: rgba(24, 133, 234, 0.2);
color: #1b7cc4;
}
}
}
}
</style>

View File

@@ -1,30 +0,0 @@
<template>
<div class="topology-map">
<!-- <img class="img-topology" src="../../../img/background/topology.svg"> -->
<div class="img-topology"></div>
</div>
</template>
<script>
export default {
name: 'TopologyMap',
}
</script>
<style lang='less' scoped>
.topology-map {
width: 49%;
height: 100%;
background-image:url('../../../img/backgroundFourCorner.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.img-topology {
width: 100%;
height: 100%;
background-image:url('../../../img/background/topology.svg');
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
}
</style>

View File

@@ -1,217 +0,0 @@
<template>
<div class='wrapper' v-loading="loading">
<div class='chart' id='chart' style="width: 100%; height: 100%;"></div>
</div>
</template>
<script>
import { getTargetsResponse } from './mock.js'
export default {
data () {
return {
loading: false,
worldChart: {},
target_id: '',
NodeList: [],
namemap: {},
geoCoordMap: {}
}
},
props:{
},
watch: {
'$store.state.range.targetId': {
handler(newVal, oldVal) {
this.target_id = newVal
this.init()
},
immediate: true
}
},
created() {
},
mounted () {
},
methods: {
init() {
this.initData()
},
initData() {
// 获取world.json里面国家数据和坐标数据
var json = require('/src/api/world.json')
this.geoCoordMap = json.trapeze
this.namemap = json.namemap
// 获取接口节点数据
// this.NodeList = getTargetsResponse?.result
const reqParams = {}
if (this.target_id && this.target_id !== '') {
reqParams.target_id = this.target_id
}
this.loading = true
this.$axios.get(this.$http.api.worldMap, reqParams).then(res => {
if (res.code == 200 || res.code == "OK") {
this.NodeList = res?.result
}
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false
this.initWorldMap(this.namemap, this.geoCoordMap)
})
},
initWorldMap(namemap, geoCoordMap) {
// var that = this
// 获取echarts的容器
this.worldChart = this.$echarts.init(document.getElementById("chart"));
var pointData = this.NodeList
var series = [];
[[, pointData]].forEach(function (item, i) {
series.push({
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
//涟漪特效
period: 4, //动画时间,值越小速度越快
brushType: "stroke", //波纹绘制方式 stroke, fill
scale: 4
//波纹圆环最大限制,值越大波纹越大
},
label: {
normal: {
show: true,
position: "right", //显示位置
offset: [5, 0], //偏移设置
formatter: "{b}" //圆环显示文字
},
emphasis: {
show: true
}
},
symbol: "circle",
symbolSize: function (val) {
return 5; //圆环大小
},
data: item[1].map(dataItem => {
let countryName = ''
for (let key in namemap) {
if(key.toLowerCase() === dataItem.country.replace('-', ' ').toLowerCase()) {
countryName = namemap[key]
}
}
const location = [Number(dataItem.location_info.longitude), Number(dataItem.location_info.latitude), dataItem.id]
return {
name: dataItem.nick_name,
value: location,
nodeInfo: dataItem
}
})
});
});
let option = {
// backgroundColor: '#000',
// 图表主标题
title: {
text: '世界地图展示各节点位置', // 主标题文本,支持使用 \n 换行
top: 5, // 定位 值: 'top', 'middle', 'bottom' 也可以是具体的值或者百分比
left: 'left', // 值: 'left', 'center', 'right' 同上
textStyle: { // 文本样式
fontSize: 14,
fontWeight: 400,
color: '#ffffff'
}
},
//悬浮提示
tooltip: {
trigger: "item",
// backgroundColor: "#1540a1",
borderColor: "#FFFFFF",
showDelay: 0,
hideDelay: 0,
// enterable: true,
transitionDuration: 0,
// extraCssText: "z-index:100",formatter
formatter: function (params, ticket, callback) {
// console.log(params)
//根据业务自己拓展要显示的内容
var res = "";
const { name, value, nodeInfo } = params.data
res = "<span style='display:inline-block; text-align:left;'>id:" + value[2]
+ "<br/>昵称:" + name.toString()
+ "<br/>国家:" + nodeInfo.location_info.country
+ "<br/>ip:" + nodeInfo.ip
+ "<br/>经纬度:" + value[0] + ',' + value[1]
+ "</span>";
return res;
}
},
visualMap: {
//图例值控制
show: false,
min: 0,
max: 1000,
text:['max','min'],
realtime: false,
calculable: true,
color: ['orangered']
// color: ['#0064d0','#c3e0ff'],
},
geo: {
map: "world",
label: {
emphasis: {
show: false
}
},
roam: true, //是否允许缩放
layoutCenter: ["50%", "50%"], //地图位置
layoutSize: "180%",
itemStyle: {
normal: {
areaColor: '#0064d0', //地图背景色
// color: ["#04284e"], //地图背景色
borderColor: "#5bc1c9" //省市边界线
},
emphasis: {
color: "rgba(37, 43, 61, .5)" //选中区域的颜色
// areaColor: '#3742ff' // 选中区域的颜色
}
},
nameMap: this.namemap
},
series: series,
loading: true // 开启Loading效果
};
this.worldChart.setOption(option);
//点击事件,根据点击某个节点跳转到节点详情页
this.worldChart.on("click", params => {
if (params.value) {
this.$store.commit('range/setNodeId', params.value[2])
//保存是否正在采集流量
this.$store.commit('node/setStartTraffic', params?.data?.nodeInfo?.collecting)
//保存是否正在结束采集流量
this.$store.commit('node/setEndTraffic', params?.data?.nodeInfo?.analysing)
this.$router.push({ name: 'nodeDetail'})
}
});
}
// drawChart () {
// this.initWorldMap(this.namemap, this.geoCoordMap)
// }
}
}
</script>
<style lang="less" scoped>
.wrapper {
width: 100%;
height: 100%;
}
.wrapper .chart {
width: 100%;
margin:0 auto;
height: 100%;
background-size: 100% 100%;
}
</style>

View File

@@ -1,68 +0,0 @@
<template>
<div :class="['world-map', {'is--maximize': isFullscreen}]">
<Map ref="worldMap"></Map>
<span class="icon-span" slot="label">
<svg-icon class="icon-zoom" :icon-class="fullscreenIcon" @click="zoomEvent"></svg-icon>
</span>
</div>
</template>
<script>
import Map from './Map'
export default {
name: 'WorldMap',
components: { Map },
data() {
return {
isFullscreen: false,
fullscreenIcon: 'fullscreen'
}
},
methods: {
zoomEvent() {
this.isFullscreen = !this.isFullscreen
this.fullscreenIcon = this.isFullscreen ? 'narrow' : 'fullscreen'
this.$nextTick(() => {
this.$refs.worldMap.worldChart.resize()
})
},
}
}
</script>
<style lang='less' scoped>
.world-map {
position: relative;
width: 49%;
height: 100%;
background-image:url('../../../img/backgroundFourCorner.png');
background-repeat: no-repeat;
background-size: 100% 100%;
.icon-span {
position: absolute;
top: 10px;
right: -10px;
padding: 12px 20px;
.icon-zoom {
font-size: 30px;
color: #02DDEA;
margin: 0 15px;
}
}
}
.is--maximize {
position: fixed;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
padding: 0.5em 1em;
// background-color: #17234e;
background: url(../../../img/background/bgMain.svg);
background-repeat: no-repeat; /* 可选,防止图像重复 */
background-position: center; /* 居中显示 */
background-size: cover; /* 宽度为100%,高度自适应保持宽高比 */
z-index: 1000;
}
</style>

View File

@@ -1,113 +0,0 @@
const getTargetsResponse = {
"code": 200,
"message": "success",
"result": [
{
"setting_id": 1,
"traffic_collect_task_id": null,
"id": 1,
"ip": "171.25.193.64",
"pod_name": "target-1-da-1-6cddf4649d-mfpx7",
"nick_name": "daoranjcqlil",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:32",
"complete_time": "2023-12-29T19:00:17",
"role": "da",
"country": "sweden",
"cidr": "171.25.193.0/24"
},
{
"setting_id": 2,
"traffic_collect_task_id": null,
"id": 2,
"ip": "193.31.27.64",
"pod_name": "target-1-relay-2-8df66cb66-slhnv",
"nick_name": "relayjknwkekrof",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:34",
"complete_time": "2023-12-29T19:05:01",
"role": "relay",
"country": "germany",
"cidr": "193.31.27.0/24"
},
{
"setting_id": 3,
"traffic_collect_task_id": null,
"id": 3,
"ip": "80.228.207.64",
"pod_name": "target-1-relay-3-6f57bf5448-snwgk",
"nick_name": "relayuyunnharjw",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:36",
"complete_time": "2023-12-29T19:05:00",
"role": "relay",
"country": "germany",
"cidr": "80.228.207.0/24"
},
{
"setting_id": 4,
"traffic_collect_task_id": null,
"id": 4,
"ip": "116.202.169.64",
"pod_name": "target-1-relay-4-557dcdfbd7-4lx5p",
"nick_name": "relayslwwaewlzs",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:38",
"complete_time": "2023-12-29T19:04:00",
"role": "relay",
"country": "germany",
"cidr": "116.202.169.0/24"
},
{
"setting_id": 6,
"traffic_collect_task_id": null,
"id": 6,
"ip": "84.19.188.64",
"pod_name": "target-1-relay-6-8559fc5858-j2v5m",
"nick_name": "relayzvrsfipwmd",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:46",
"complete_time": "2023-12-29T19:08:06",
"role": "relay",
"country": "germany",
"cidr": "84.19.188.0/24"
},
{
"setting_id": 7,
"traffic_collect_task_id": null,
"id": 7,
"ip": "45.138.16.0",
"pod_name": "target-1-exit-7-78984bf859-2hmnc",
"nick_name": "exitpzidrsshzi",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:48",
"complete_time": "2023-12-29T19:07:12",
"role": "exit",
"country": "netherlands",
"cidr": "45.138.16.0/24"
},
{
"setting_id": 8,
"traffic_collect_task_id": null,
"id": 8,
"ip": "77.91.68.64",
"pod_name": "target-1-exit-8-865cf99948-gw7br",
"nick_name": "exitgqnnvqsoux",
"status": "complete",
"onion": null,
"create_time": "2023-12-29T18:50:49",
"complete_time": "2023-12-29T19:04:56",
"role": "exit",
"country": "russia",
"cidr": "77.91.68.0/24"
}
]
}
export { getTargetsResponse }

View File

@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>echarts世界地图闪烁点 www.bootstrapmb.com</title>
<script type="text/javascript" src="echarts.min.js"></script>
<script type="text/javascript" src="world_new.js"></script>
</head>
<body>
<div id="main" style="width: 100%;height:120vh;"></div>
<script>
// 获取echarts的容器
var chart = echarts.init(document.getElementById("main"));
var geoCoordMap = {
北京: [116.28, 39.54],
// 杭州: [120.10, 30.15],
// 南宁: [108.479, 23.1152],
// 广州: [113.5107, 23.2196],
重庆: [107.7539, 30.1904],
上海: [121.4648, 31.2891],
尼日利亚: [-4.388361, 11.186148],
洛杉矶: [-118.24311, 34.052713],
香港: [114.195466, 22.282751],
芝加哥: [-87.801833, 41.870975],
加纳库马西: [-4.62829, 7.72415],
曼彻斯特: [-1.657222, 51.886863],
汉堡: [10.01959, 54.38474],
阿拉木图: [45.326912, 41.101891],
伊尔库茨克: [89.116876, 67.757906],
巴西: [-48.678945, -10.493623],
埃及: [31.815593, 31.418032],
巴塞罗纳: [2.175129, 41.385064],
柬埔寨: [104.88659, 11.545469],
米兰: [9.189948, 45.46623],
蒙得维的亚: [-56.162231, -34.901113],
莫桑比克: [32.608571, -25.893473],
阿尔及尔: [3.054275, 36.753027],
阿联酋迪拜: [55.269441, 25.204514],
布达佩斯: [17.108519, 48.179162],
悉尼: [150.993137, -33.675509],
加州: [-121.910642, 41.38028],
墨尔本: [144.999416, -37.781726],
墨西哥: [-99.094092, 19.365711],
温哥华: [-123.023921, 49.311753]
};
var BJData = [[{
name: "北京",
value: 12580
}],[{
name: "重庆",
value: 10000000
}],[{
name: "上海",
value: 9100
}], [{
name: "尼日利亚",
value: 9100
}], [{
name: "洛杉矶",
value: 2370
}], [{
name: "香港",
value: 3130
}], [{
name: "芝加哥",
value: 2350
}], [{
name: "加纳库马西",
value: 5120
}], [{
name: "曼彻斯特",
value: 3110
}], [{
name: "汉堡",
value: 6280
}], [{
name: "阿拉木图",
value: 7255
}], [{
name: "伊尔库茨克",
value: 8125
}], [{
name: "巴西",
value: 3590
}], [{
name: "埃及",
value: 3590
}], [{
name: "巴塞罗纳",
value: 3590
}], [{
name: "柬埔寨",
value: 3590
}], [{
name: "米兰",
value: 3590
}], [{
name: "蒙得维的亚",
value: 3590
}], [{
name: "莫桑比克",
value: 3590
}], [{
name: "阿尔及尔",
value: 31590
}], [{
name: "阿联酋迪拜",
value: 13590
}], [{
name: "布达佩斯",
value: 23590
}], [{
name: "悉尼",
value: 3590
}], [{
name: "加州",
value: 3590
}], [{
name: "墨尔本",
value: 3590
}], [{
name: "墨西哥",
value: 3590
}], [{
name: "温哥华",
value: 3590
}]];
var series = [];
[[, BJData]].forEach(function (item, i) {
series.push({
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
//涟漪特效
period: 4, //动画时间,值越小速度越快
brushType: "stroke", //波纹绘制方式 stroke, fill
scale: 4
//波纹圆环最大限制,值越大波纹越大
},
label: {
normal: {
show: true,
position: "right", //显示位置
offset: [5, 0], //偏移设置
formatter: "{b}" //圆环显示文字
},
emphasis: {
show: true
}
},
symbol: "circle",
symbolSize: function (val) {
var level = 0 ;
var state= Math.floor(val[2]/5000) ;
switch (state)
{
case 0: level=0; break;
case 1: level=1; break;
case 2: level=2; break;
case 3: level=3; break;
case 4: level=4; break;
case 5: level=5; break;
case 6: level=6; break;
case 7: level=7; break;
case 8: level=8; break;
case 9: level=9; break;
default: level=10;
}
return 5 + level; //圆环大小
},
data: item[1].map(function (dataItem) {
return {
name: dataItem[0].name/*+"\n"+dataItem[0].value*/,
value: geoCoordMap[dataItem[0].name]
.concat([dataItem[0].value])
};
})
});
});
option = {
backgroundColor: '#000',
//悬浮提示
tooltip: {
trigger: "item",
backgroundColor: "#1540a1",
borderColor: "#FFFFCC",
showDelay: 0,
hideDelay: 0,
// enterable: true,
transitionDuration: 0,
// extraCssText: "z-index:100",formatter
formatter: function (params, ticket, callback) {
//根据业务自己拓展要显示的内容
var res = "";
var name = params.name;
var value = params.value[params.seriesIndex + 1];
res = "<span style='color:#fff;'>" + name.toString().split(' ')[0]
+ "</span><br/>爬虫:" + name.toString().split(' ')[1];
return res;
}
},
visualMap: {
//图例值控制
min: 0,
max: 10000,
text:['High','Low'],
show: false,
calculable: true,
//color: ["#0bc7f3"],
color: ['orangered','yellow','lightskyblue']
},
geo: {
map: "world",
label: {
emphasis: {
show: false
}
},
roam: true, //是否允许缩放
layoutCenter: ["50%", "50%"], //地图位置
layoutSize: "180%",
itemStyle: {
normal: {
color: ["#04284e"], //地图背景色
//color: ['orangered','yellow','lightskyblue']
borderColor: "#5bc1c9" //省市边界线
},
emphasis: {
color: "rgba(37, 43, 61, .5)" //悬浮背景
}
}
},
series: series
};
chart.setOption(option);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long