添加SSH连接信息存储
This commit is contained in:
parent
4647fc030a
commit
da711b157b
@ -99,6 +99,15 @@
|
|||||||
<artifactId>influxdb-client-java</artifactId>
|
<artifactId>influxdb-client-java</artifactId>
|
||||||
<version>6.6.0</version>
|
<version>6.6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jcraft</groupId>
|
||||||
|
<artifactId>jsch</artifactId>
|
||||||
|
<version>0.1.55</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
@ -53,6 +53,7 @@ public class SecurityConfiguration {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
return http
|
return http
|
||||||
.authorizeHttpRequests(conf -> conf
|
.authorizeHttpRequests(conf -> conf
|
||||||
|
.requestMatchers("/terminal/**").permitAll()
|
||||||
.requestMatchers("/api/auth/**", "/error").permitAll()
|
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||||
.requestMatchers("/monitor/**").permitAll()
|
.requestMatchers("/monitor/**").permitAll()
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
|
@ -5,10 +5,8 @@ import com.example.entity.dto.Account;
|
|||||||
import com.example.entity.vo.request.RenameClientVO;
|
import com.example.entity.vo.request.RenameClientVO;
|
||||||
import com.example.entity.vo.request.RenameNodeVO;
|
import com.example.entity.vo.request.RenameNodeVO;
|
||||||
import com.example.entity.vo.request.RuntimeDetailVO;
|
import com.example.entity.vo.request.RuntimeDetailVO;
|
||||||
import com.example.entity.vo.response.ClientDetailsVO;
|
import com.example.entity.vo.request.SshConnectionVO;
|
||||||
import com.example.entity.vo.response.ClientPreviewVO;
|
import com.example.entity.vo.response.*;
|
||||||
import com.example.entity.vo.response.ClientSimpleVO;
|
|
||||||
import com.example.entity.vo.response.RuntimeDetailsVO;
|
|
||||||
import com.example.service.AccountService;
|
import com.example.service.AccountService;
|
||||||
import com.example.service.ClientService;
|
import com.example.service.ClientService;
|
||||||
import com.example.utils.Const;
|
import com.example.utils.Const;
|
||||||
@ -128,6 +126,29 @@ public class MonitorController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ssh-save")
|
||||||
|
public RestBean<Void> saveSshConnection(@RequestBody @Valid SshConnectionVO vo,
|
||||||
|
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||||
|
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||||
|
if(this.permissionCheck(userId, userRole, vo.getId())) {
|
||||||
|
service.saveClientSshConnection(vo);
|
||||||
|
return RestBean.success();
|
||||||
|
} else {
|
||||||
|
return RestBean.noPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ssh")
|
||||||
|
public RestBean<SshSettingsVO> sshSettings(int clientId,
|
||||||
|
@RequestAttribute(Const.ATTR_USER_ID) int userId,
|
||||||
|
@RequestAttribute(Const.ATTR_USER_ROLE) String userRole) {
|
||||||
|
if(this.permissionCheck(userId, userRole, clientId)) {
|
||||||
|
return RestBean.success(service.sshSettings(clientId));
|
||||||
|
} else {
|
||||||
|
return RestBean.noPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Integer> accountAccessClients(int uid) {
|
private List<Integer> accountAccessClients(int uid) {
|
||||||
Account account = accountService.getById(uid);
|
Account account = accountService.getById(uid);
|
||||||
return account.getClientList();
|
return account.getClientList();
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.entity.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.example.entity.BaseData;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("db_client_ssh")
|
||||||
|
public class ClientSsh implements BaseData {
|
||||||
|
@TableId
|
||||||
|
Integer id;
|
||||||
|
Integer port;
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.entity.vo.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SshConnectionVO {
|
||||||
|
int id;
|
||||||
|
@NotNull
|
||||||
|
int port;
|
||||||
|
@NotNull
|
||||||
|
@Length(min = 1)
|
||||||
|
String username;
|
||||||
|
@NotNull
|
||||||
|
@Length(min = 1)
|
||||||
|
String password;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.entity.vo.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SshSettingsVO {
|
||||||
|
String ip;
|
||||||
|
int port = 22;
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.example.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.example.entity.dto.ClientSsh;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ClientSshMapper extends BaseMapper<ClientSsh> {
|
||||||
|
}
|
@ -2,14 +2,8 @@ package com.example.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.example.entity.dto.Client;
|
import com.example.entity.dto.Client;
|
||||||
import com.example.entity.vo.request.ClientDetailVO;
|
import com.example.entity.vo.request.*;
|
||||||
import com.example.entity.vo.request.RenameClientVO;
|
import com.example.entity.vo.response.*;
|
||||||
import com.example.entity.vo.request.RenameNodeVO;
|
|
||||||
import com.example.entity.vo.request.RuntimeDetailVO;
|
|
||||||
import com.example.entity.vo.response.ClientDetailsVO;
|
|
||||||
import com.example.entity.vo.response.ClientPreviewVO;
|
|
||||||
import com.example.entity.vo.response.ClientSimpleVO;
|
|
||||||
import com.example.entity.vo.response.RuntimeDetailsVO;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -28,4 +22,6 @@ public interface ClientService extends IService<Client> {
|
|||||||
RuntimeDetailsVO clientRuntimeDetailsHistory(int clientId);
|
RuntimeDetailsVO clientRuntimeDetailsHistory(int clientId);
|
||||||
RuntimeDetailVO clientRuntimeDetailsNow(int clientId);
|
RuntimeDetailVO clientRuntimeDetailsNow(int clientId);
|
||||||
void deleteClient(int clientId);
|
void deleteClient(int clientId);
|
||||||
|
void saveClientSshConnection(SshConnectionVO vo);
|
||||||
|
SshSettingsVO sshSettings(int clientId);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
package com.example.service;
|
||||||
|
|
||||||
|
public interface TerminalService {
|
||||||
|
}
|
@ -4,16 +4,12 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.example.entity.dto.Client;
|
import com.example.entity.dto.Client;
|
||||||
import com.example.entity.dto.ClientDetail;
|
import com.example.entity.dto.ClientDetail;
|
||||||
import com.example.entity.vo.request.ClientDetailVO;
|
import com.example.entity.dto.ClientSsh;
|
||||||
import com.example.entity.vo.request.RenameClientVO;
|
import com.example.entity.vo.request.*;
|
||||||
import com.example.entity.vo.request.RenameNodeVO;
|
import com.example.entity.vo.response.*;
|
||||||
import com.example.entity.vo.request.RuntimeDetailVO;
|
|
||||||
import com.example.entity.vo.response.ClientDetailsVO;
|
|
||||||
import com.example.entity.vo.response.ClientPreviewVO;
|
|
||||||
import com.example.entity.vo.response.ClientSimpleVO;
|
|
||||||
import com.example.entity.vo.response.RuntimeDetailsVO;
|
|
||||||
import com.example.mapper.ClientDetailMapper;
|
import com.example.mapper.ClientDetailMapper;
|
||||||
import com.example.mapper.ClientMapper;
|
import com.example.mapper.ClientMapper;
|
||||||
|
import com.example.mapper.ClientSshMapper;
|
||||||
import com.example.service.ClientService;
|
import com.example.service.ClientService;
|
||||||
import com.example.utils.InfluxDbUtils;
|
import com.example.utils.InfluxDbUtils;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@ -37,6 +33,9 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
|
|||||||
@Resource
|
@Resource
|
||||||
private ClientDetailMapper detailMapper;
|
private ClientDetailMapper detailMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ClientSshMapper sshMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private InfluxDbUtils influx;
|
private InfluxDbUtils influx;
|
||||||
|
|
||||||
@ -163,6 +162,33 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
|
|||||||
lastRuntime.remove(clientId);
|
lastRuntime.remove(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveClientSshConnection(SshConnectionVO vo) {
|
||||||
|
Client client = this.getById(vo.getId());
|
||||||
|
if(client == null) return;
|
||||||
|
ClientSsh ssh = new ClientSsh();
|
||||||
|
BeanUtils.copyProperties(vo, ssh);
|
||||||
|
if(Objects.nonNull(sshMapper.selectById(client.getId()))) {
|
||||||
|
sshMapper.updateById(ssh);
|
||||||
|
} else {
|
||||||
|
sshMapper.insert(ssh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SshSettingsVO sshSettings(int clientId) {
|
||||||
|
ClientDetail detail = detailMapper.selectById(clientId);
|
||||||
|
ClientSsh ssh = sshMapper.selectById(clientId);
|
||||||
|
SshSettingsVO vo;
|
||||||
|
if(ssh == null) {
|
||||||
|
vo = new SshSettingsVO();
|
||||||
|
} else {
|
||||||
|
vo = ssh.asViewObject(SshSettingsVO.class);
|
||||||
|
}
|
||||||
|
vo.setIp(detail.getIp());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isOnline(RuntimeDetailVO runtime) {
|
private boolean isOnline(RuntimeDetailVO runtime) {
|
||||||
return runtime != null && System.currentTimeMillis() - runtime.getTimestamp() < 60 * 1000;
|
return runtime != null && System.currentTimeMillis() - runtime.getTimestamp() < 60 * 1000;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.example.service.impl;
|
||||||
|
|
||||||
|
import com.example.service.TerminalService;
|
||||||
|
import jakarta.websocket.*;
|
||||||
|
import jakarta.websocket.server.ServerEndpoint;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@ServerEndpoint("/terminal/{clientId}")
|
||||||
|
public class TerminalServiceImpl implements TerminalService {
|
||||||
|
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session) throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClose
|
||||||
|
public void onClose(Session session) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(Session session, String message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Session session, Throwable error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ import {useClipboard} from "@vueuse/core";
|
|||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import {cpuNameToImage, fitToRightByteUnit, osNameToIcon, percentageToStatus, rename} from "@/tools";
|
import {cpuNameToImage, fitToRightByteUnit, osNameToIcon, percentageToStatus, rename} from "@/tools";
|
||||||
import RuntimeHistroy from "@/component/RuntimeHistroy.vue";
|
import RuntimeHistroy from "@/component/RuntimeHistroy.vue";
|
||||||
import {Delete} from "@element-plus/icons-vue";
|
import {Connection, Delete, FolderOpened} from "@element-plus/icons-vue";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -16,7 +16,7 @@ const props = defineProps({
|
|||||||
update: Function,
|
update: Function,
|
||||||
show: Boolean
|
show: Boolean
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['delete'])
|
const emits = defineEmits(['delete', 'terminal'])
|
||||||
|
|
||||||
const details = reactive({
|
const details = reactive({
|
||||||
base: {},
|
base: {},
|
||||||
@ -100,14 +100,17 @@ watch(() => props.id, init, { immediate: true })
|
|||||||
<div class="client-details"
|
<div class="client-details"
|
||||||
v-loading="Object.keys(details.base).length === 0">
|
v-loading="Object.keys(details.base).length === 0">
|
||||||
<div v-if="Object.keys(details.base).length">
|
<div v-if="Object.keys(details.base).length">
|
||||||
<div style="display: flex;justify-content: space-between">
|
<div style="margin-bottom: 15px">
|
||||||
|
<el-button :icon="Connection" type="info" size="small" plain
|
||||||
|
@click="emits('terminal', id)">SSH远程连接</el-button>
|
||||||
|
<el-button :icon="FolderOpened" type="primary" size="small" plain>在线SFTP文件管理</el-button>
|
||||||
|
<el-button :icon="Delete" type="danger" size="small"
|
||||||
|
@click="deleteClient" plain>删除此主机</el-button>
|
||||||
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="fa-solid fa-server"></i>
|
<i class="fa-solid fa-server"></i>
|
||||||
服务器信息
|
服务器信息
|
||||||
</div>
|
</div>
|
||||||
<el-button :icon="Delete" type="danger"
|
|
||||||
@click="deleteClient" plain text>删除此主机</el-button>
|
|
||||||
</div>
|
|
||||||
<el-divider style="margin: 10px 0"/>
|
<el-divider style="margin: 10px 0"/>
|
||||||
<div class="details-list">
|
<div class="details-list">
|
||||||
<div>
|
<div>
|
||||||
|
89
itbaima-monitor-web/src/component/TerminalWindow.vue
Normal file
89
itbaima-monitor-web/src/component/TerminalWindow.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<script setup>
|
||||||
|
import {reactive, ref, watch} from "vue";
|
||||||
|
import {get, post} from "@/net";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: Number
|
||||||
|
})
|
||||||
|
|
||||||
|
const connection = reactive({
|
||||||
|
ip: '',
|
||||||
|
port: 22,
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
port: [
|
||||||
|
{ required: true, message: '请输入端口', trigger: ['blur', 'change'] },
|
||||||
|
],
|
||||||
|
username: [
|
||||||
|
{ required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: '请输入密码', trigger: ['blur', 'change'] },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
|
||||||
|
function saveConnection() {
|
||||||
|
formRef.value.validate((isValid) => {
|
||||||
|
if(isValid) {
|
||||||
|
post('/api/monitor/ssh-save', {
|
||||||
|
...connection,
|
||||||
|
id: props.id
|
||||||
|
}, () => {
|
||||||
|
ElMessage.success('正在连接')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.id, id => {
|
||||||
|
connection.ip = ''
|
||||||
|
get(`/api/monitor/ssh?clientId=${id}`, data => Object.assign(connection, data))
|
||||||
|
}, {immediate: true})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="terminal-main">
|
||||||
|
<div class="login" v-loading="!connection.ip">
|
||||||
|
<i style="font-size: 50px" class="fa-solid fa-terminal"></i>
|
||||||
|
<div style="margin-top: 10px;font-weight: bold;font-size: 20px">服务端连接信息</div>
|
||||||
|
<el-form style="width: 400px;margin: 20px auto" :model="connection"
|
||||||
|
:rules="rules" ref="formRef" label-width="100">
|
||||||
|
<div style="display: flex;gap: 10px">
|
||||||
|
<el-form-item style="width: 100%" label="服务器IP地址" prop="ip">
|
||||||
|
<el-input v-model="connection.ip" disabled/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item style="width: 80px" prop="port" label-width="0">
|
||||||
|
<el-input placeholder="端口" v-model="connection.port"/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<el-form-item prop="username" label="登录用户名">
|
||||||
|
<el-input placeholder="请输入用户名..." v-model="connection.username"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password" label="登录密码">
|
||||||
|
<el-input placeholder="请输入密码..." type="password" v-model="connection.password"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-button type="success" @click="saveConnection" plain>立即连接</el-button>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.terminal-main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.login {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 50px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,6 +7,7 @@ import {Plus} from "@element-plus/icons-vue";
|
|||||||
import RegisterCard from "@/component/RegisterCard.vue";
|
import RegisterCard from "@/component/RegisterCard.vue";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import TerminalWindow from "@/component/TerminalWindow.vue";
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -42,6 +43,16 @@ const updateList = () => {
|
|||||||
}
|
}
|
||||||
setInterval(updateList, 10000)
|
setInterval(updateList, 10000)
|
||||||
updateList()
|
updateList()
|
||||||
|
|
||||||
|
const openTerminal = id => {
|
||||||
|
terminal.show = true
|
||||||
|
terminal.id = id
|
||||||
|
detail.show = false
|
||||||
|
}
|
||||||
|
const terminal = reactive({
|
||||||
|
show: false,
|
||||||
|
id: detail.id
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -71,13 +82,26 @@ updateList()
|
|||||||
</div>
|
</div>
|
||||||
<el-drawer size="520" :show-close="false" v-model="detail.show"
|
<el-drawer size="520" :show-close="false" v-model="detail.show"
|
||||||
:with-header="false" v-if="list.length" @close="detail.id = -1">
|
:with-header="false" v-if="list.length" @close="detail.id = -1">
|
||||||
<client-details :id="detail.id" :update="updateList" :show="detail.show" @delete="detail.show = false"/>
|
<client-details :id="detail.id" :update="updateList" :show="detail.show"
|
||||||
|
@delete="detail.show = false" @terminal="openTerminal"/>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
<el-drawer style="width: 600px;margin: 10px auto" v-model="register.show"
|
<el-drawer style="width: 600px;margin: 10px auto" v-model="register.show"
|
||||||
direction="btt" :show-close="false" :with-header="false"
|
direction="btt" :show-close="false" :with-header="false"
|
||||||
:size="320" @open="refreshToken">
|
:size="320" @open="refreshToken">
|
||||||
<register-card :token="register.token"/>
|
<register-card :token="register.token"/>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
<el-drawer style="width: 800px" :size="500" direction="btt" v-model="terminal.show"
|
||||||
|
:close-on-click-modal="false">
|
||||||
|
<template #header>
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 18px;color: dodgerblue;font-weight: bold;">SSH远程连接</div>
|
||||||
|
<div style="font-size: 14px">
|
||||||
|
远程连接的建立将由服务端完成,因此在内网环境下也可以正常使用。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<terminal-window :id="terminal.id"/>
|
||||||
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user