完成SSH连接信息存储

This commit is contained in:
柏码の讲师 2023-12-14 17:33:06 +08:00
parent 77b5b2ef7f
commit f06c2fd68f
10 changed files with 239 additions and 25 deletions

View File

@ -5,10 +5,8 @@ import com.example.entity.dto.Account;
import com.example.entity.vo.request.RenameClientVO;
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.RuntimeHistoryVO;
import com.example.entity.vo.request.SshConnectionVO;
import com.example.entity.vo.response.*;
import com.example.service.AccountService;
import com.example.service.ClientService;
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) {
Account account = accountService.getById(uid);
return account.getClientList();

View File

@ -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;
}

View File

@ -0,0 +1,17 @@
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;
int port;
@NotNull
@Length(min = 1)
String username;
@NotNull
@Length(min = 1)
String password;
}

View File

@ -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;
}

View File

@ -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> {
}

View File

@ -2,14 +2,8 @@ package com.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.dto.Client;
import com.example.entity.vo.request.ClientDetailVO;
import com.example.entity.vo.request.RenameClientVO;
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.RuntimeHistoryVO;
import com.example.entity.vo.request.*;
import com.example.entity.vo.response.*;
import java.util.List;
@ -28,4 +22,6 @@ public interface ClientService extends IService<Client> {
RuntimeHistoryVO clientRuntimeDetailsHistory(int clientId);
RuntimeDetailVO clientRuntimeDetailsNow(int clientId);
void deleteClient(int clientId);
void saveClientSshConnection(SshConnectionVO vo);
SshSettingsVO sshSettings(int clientId);
}

View File

@ -4,16 +4,12 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.dto.Client;
import com.example.entity.dto.ClientDetail;
import com.example.entity.vo.request.ClientDetailVO;
import com.example.entity.vo.request.RenameClientVO;
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.RuntimeHistoryVO;
import com.example.entity.dto.ClientSsh;
import com.example.entity.vo.request.*;
import com.example.entity.vo.response.*;
import com.example.mapper.ClientDetailMapper;
import com.example.mapper.ClientMapper;
import com.example.mapper.ClientSshMapper;
import com.example.service.ClientService;
import com.example.utils.InfluxDbUtils;
import jakarta.annotation.PostConstruct;
@ -39,6 +35,9 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
@Resource
InfluxDbUtils influx;
@Resource
ClientSshMapper sshMapper;
@PostConstruct
public void initClientCache() {
clientTokenCache.clear();
@ -160,6 +159,33 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
currentRuntime.remove(clientId);
}
@Override
public void saveClientSshConnection(SshConnectionVO vo) {
Client client = clientIdCache.get(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) {
return runtime != null && System.currentTimeMillis() - runtime.getTimestamp() < 60 * 1000;
}

View File

@ -4,7 +4,7 @@ import {get, post} from "@/net";
import {copyIp, cpuNameToImage, fitByUnit, osNameToIcon, percentageToStatus, rename} from "@/tools";
import {ElMessage, ElMessageBox} from "element-plus";
import RuntimeHistory from "@/component/RuntimeHistory.vue";
import {Delete} from "@element-plus/icons-vue";
import {Connection, Delete} from "@element-plus/icons-vue";
const locations = [
{name: 'cn', desc: '中国大陆'},
@ -20,7 +20,7 @@ const props = defineProps({
id: Number,
update: Function
})
const emits = defineEmits(['delete'])
const emits = defineEmits(['delete', 'terminal'])
const details = reactive({
base: {},
@ -101,8 +101,12 @@ watch(() => props.id, init, { immediate: true })
<i class="fa-solid fa-server"></i>
服务器信息
</div>
<el-button :icon="Delete" type="danger"
@click="deleteClient" plain text>删除此主机</el-button>
<div>
<el-button :icon="Connection" type="info"
@click="emits('terminal', id)" plain text>SSH远程连接</el-button>
<el-button :icon="Delete" type="danger" style="margin-left: 0"
@click="deleteClient" plain text>删除此主机</el-button>
</div>
</div>
<el-divider style="margin: 10px 0"/>
<div class="details-list">

View File

@ -0,0 +1,91 @@
<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 => {
if(id !== -1) {
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>

View File

@ -7,6 +7,7 @@ import RegisterCard from "@/component/RegisterCard.vue";
import {Plus} from "@element-plus/icons-vue";
import {useRoute} from "vue-router";
import {useStore} from "@/store";
import TerminalWindow from "@/component/TerminalWindow.vue";
const locations = [
{name: 'cn', desc: '中国大陆'},
@ -54,6 +55,16 @@ const register = reactive({
token: ''
})
const refreshToken = () => get('/api/monitor/register', token => register.token = token)
function openTerminal(id) {
terminal.show = true
terminal.id = id
detail.show = false
}
const terminal = reactive({
show: false,
id: -1
})
</script>
<template>
@ -84,12 +95,24 @@ const refreshToken = () => get('/api/monitor/register', token => register.token
<el-empty description="还没有任何主机哦,点击右上角添加一个吧" v-else/>
<el-drawer size="520" :show-close="false" v-model="detail.show"
:with-header="false" v-if="list.length" @close="detail.id = -1">
<client-details :id="detail.id" :update="updateList" @delete="updateList"/>
<client-details :id="detail.id" :update="updateList" @delete="updateList" @terminal="openTerminal"/>
</el-drawer>
<el-drawer v-model="register.show" direction="btt" :with-header="false"
style="width: 600px;margin: 10px auto" size="320" @open="refreshToken">
<register-card :token="register.token"/>
</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>
</template>