mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-08 01:20:07 +08:00
feat: 完成 ssh 配置前端界面实现
This commit is contained in:
parent
a8df6d0668
commit
da54794aca
@ -26,9 +26,10 @@ var (
|
|||||||
|
|
||||||
cronjobService = service.NewICronjobService()
|
cronjobService = service.NewICronjobService()
|
||||||
|
|
||||||
hostService = service.NewIHostService()
|
hostService = service.NewIHostService()
|
||||||
groupService = service.NewIGroupService()
|
groupService = service.NewIGroupService()
|
||||||
fileService = service.NewIFileService()
|
fileService = service.NewIFileService()
|
||||||
|
sshService = service.NewISSHService()
|
||||||
firewallService = service.NewIFirewallService()
|
firewallService = service.NewIFirewallService()
|
||||||
|
|
||||||
settingService = service.NewISettingService()
|
settingService = service.NewISettingService()
|
||||||
|
51
backend/app/api/v1/ssh.go
Normal file
51
backend/app/api/v1/ssh.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Tags SSH
|
||||||
|
// @Summary Load host ssh setting info
|
||||||
|
// @Description 加载 SSH 配置信息
|
||||||
|
// @Success 200 {object} dto.SSHInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /host/ssh/search [post]
|
||||||
|
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||||
|
info, err := sshService.GetSSHInfo()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags SSH
|
||||||
|
// @Summary Update host ssh setting
|
||||||
|
// @Description 更新 SSH 配置
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SettingUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /host/ssh/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
|
||||||
|
func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||||
|
var req dto.SettingUpdate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sshService.Update(req.Key, req.Value); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
15
backend/app/dto/ssh.go
Normal file
15
backend/app/dto/ssh.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type SSHInfo struct {
|
||||||
|
Port string `json:"port"`
|
||||||
|
ListenAddress string `json:"listenAddress"`
|
||||||
|
PasswordAuthentication string `json:"passwordAuthentication"`
|
||||||
|
PubkeyAuthentication string `json:"pubkeyAuthentication"`
|
||||||
|
PermitRootLogin string `json:"permitRootLogin"`
|
||||||
|
UseDNS string `json:"useDNS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateSSH struct {
|
||||||
|
EncryptionMode string `json:"encryptionMode"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
120
backend/app/service/ssh.go
Normal file
120
backend/app/service/ssh.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sshPath = "Downloads/sshd_config"
|
||||||
|
|
||||||
|
type SSHService struct{}
|
||||||
|
|
||||||
|
type ISSHService interface {
|
||||||
|
GetSSHInfo() (*dto.SSHInfo, error)
|
||||||
|
Update(key, value string) error
|
||||||
|
GenerateSSH(req dto.GenerateSSH) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewISSHService() ISSHService {
|
||||||
|
return &SSHService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||||
|
data := dto.SSHInfo{
|
||||||
|
Port: "22",
|
||||||
|
ListenAddress: "0.0.0.0",
|
||||||
|
PasswordAuthentication: "yes",
|
||||||
|
PubkeyAuthentication: "yes",
|
||||||
|
PermitRootLogin: "yes",
|
||||||
|
UseDNS: "yes",
|
||||||
|
}
|
||||||
|
sshConf, err := os.ReadFile(sshPath)
|
||||||
|
if err != nil {
|
||||||
|
return &data, err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(sshConf), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "Port ") {
|
||||||
|
data.Port = strings.ReplaceAll(line, "Port ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "ListenAddress ") {
|
||||||
|
data.ListenAddress = strings.ReplaceAll(line, "ListenAddress ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PasswordAuthentication ") {
|
||||||
|
data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PubkeyAuthentication ") {
|
||||||
|
data.PubkeyAuthentication = strings.ReplaceAll(line, "PubkeyAuthentication ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PermitRootLogin ") {
|
||||||
|
data.PermitRootLogin = strings.ReplaceAll(line, "PermitRootLogin ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "UseDNS ") {
|
||||||
|
data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) Update(key, value string) error {
|
||||||
|
sshConf, err := os.ReadFile(sshPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(sshConf), "\n")
|
||||||
|
newFiles := updateSSHConf(lines, key, value)
|
||||||
|
if err := settingRepo.Update(key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error {
|
||||||
|
stdout, err := cmd.Exec(fmt.Sprintf("ssh-keygen -t %s -P %s -f ~/.ssh/id_%s |echo y", req.EncryptionMode, req.Password, req.EncryptionMode))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSSHConf(oldFiles []string, param string, value interface{}) []string {
|
||||||
|
hasKey := false
|
||||||
|
var newFiles []string
|
||||||
|
for _, line := range oldFiles {
|
||||||
|
if strings.HasPrefix(line, param+" ") {
|
||||||
|
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||||
|
hasKey = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newFiles = append(newFiles, line)
|
||||||
|
}
|
||||||
|
if !hasKey {
|
||||||
|
newFiles = []string{}
|
||||||
|
for _, line := range oldFiles {
|
||||||
|
if strings.HasPrefix(line, fmt.Sprintf("#%s ", param)) && !hasKey {
|
||||||
|
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||||
|
hasKey = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newFiles = append(newFiles, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasKey {
|
||||||
|
newFiles = []string{}
|
||||||
|
newFiles = append(newFiles, oldFiles...)
|
||||||
|
newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
|
||||||
|
}
|
||||||
|
return newFiles
|
||||||
|
}
|
47
backend/app/service/ssh_test.go
Normal file
47
backend/app/service/ssh_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSfq(t *testing.T) {
|
||||||
|
data := dto.SSHInfo{
|
||||||
|
Port: "22",
|
||||||
|
ListenAddress: "0.0.0.0",
|
||||||
|
PasswordAuthentication: "yes",
|
||||||
|
PubkeyAuthentication: "yes",
|
||||||
|
PermitRootLogin: "yes",
|
||||||
|
UseDNS: "yes",
|
||||||
|
}
|
||||||
|
sshConf, err := os.ReadFile("/Downloads/sshd_config")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(sshConf), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "Port ") {
|
||||||
|
data.Port = strings.ReplaceAll(line, "Port ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "ListenAddress ") {
|
||||||
|
data.ListenAddress = strings.ReplaceAll(line, "ListenAddress ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PasswordAuthentication ") {
|
||||||
|
data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PubkeyAuthentication ") {
|
||||||
|
data.PubkeyAuthentication = strings.ReplaceAll(line, "PubkeyAuthentication ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "PermitRootLogin ") {
|
||||||
|
data.PermitRootLogin = strings.ReplaceAll(line, "PermitRootLogin ", "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "UseDNS ") {
|
||||||
|
data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
@ -35,6 +35,9 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
|
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
|
||||||
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
|
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
|
||||||
|
|
||||||
|
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)
|
||||||
|
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
||||||
|
|
||||||
hostRouter.GET("/command", baseApi.ListCommand)
|
hostRouter.GET("/command", baseApi.ListCommand)
|
||||||
hostRouter.POST("/command", baseApi.CreateCommand)
|
hostRouter.POST("/command", baseApi.CreateCommand)
|
||||||
hostRouter.POST("/command/del", baseApi.DeleteCommand)
|
hostRouter.POST("/command/del", baseApi.DeleteCommand)
|
||||||
|
@ -103,4 +103,15 @@ export namespace Host {
|
|||||||
type: string;
|
type: string;
|
||||||
rules: Array<RulePort>;
|
rules: Array<RulePort>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SSHInfo {
|
||||||
|
port: string;
|
||||||
|
listenAddress: string;
|
||||||
|
passwordAuthentication: string;
|
||||||
|
pubkeyAuthentication: string;
|
||||||
|
encryptionMode: string;
|
||||||
|
primaryKey: string;
|
||||||
|
permitRootLogin: string;
|
||||||
|
useDNS: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,3 +96,14 @@ export const updateAddrRule = (params: Host.UpdateAddrRule) => {
|
|||||||
export const batchOperateRule = (params: Host.BatchRule) => {
|
export const batchOperateRule = (params: Host.BatchRule) => {
|
||||||
return http.post(`/hosts/firewall/batch`, params);
|
return http.post(`/hosts/firewall/batch`, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ssh
|
||||||
|
export const getSSHInfo = () => {
|
||||||
|
return http.post<Host.SSHInfo>(`/hosts/ssh/search`);
|
||||||
|
};
|
||||||
|
export const updateSSH = (key: string, value: string) => {
|
||||||
|
return http.post(`/hosts/ssh/update`, { key: key, value: value });
|
||||||
|
};
|
||||||
|
export const generatePubKey = (encryptionMode: string) => {
|
||||||
|
return http.post(`/hosts/ssh/generate`, { encryptionMode: encryptionMode });
|
||||||
|
};
|
||||||
|
@ -218,6 +218,7 @@ const message = {
|
|||||||
website: '网站',
|
website: '网站',
|
||||||
project: '项目',
|
project: '项目',
|
||||||
config: '配置',
|
config: '配置',
|
||||||
|
ssh: 'SSH 配置',
|
||||||
firewall: '防火墙',
|
firewall: '防火墙',
|
||||||
ssl: '证书',
|
ssl: '证书',
|
||||||
database: '数据库',
|
database: '数据库',
|
||||||
@ -819,6 +820,31 @@ const message = {
|
|||||||
ownerHelper: 'PHP 运行环境默认用户:用户组为 1000:1000, 容器内外用户显示不一致为正常现象',
|
ownerHelper: 'PHP 运行环境默认用户:用户组为 1000:1000, 容器内外用户显示不一致为正常现象',
|
||||||
searchHelper: '支持 * 等通配符',
|
searchHelper: '支持 * 等通配符',
|
||||||
},
|
},
|
||||||
|
ssh: {
|
||||||
|
sshChange: 'SSH 配置修改',
|
||||||
|
sshChangeHelper: '确认将 SSH {0} 配置修改为 {1} 吗?',
|
||||||
|
port: '连接端口',
|
||||||
|
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
||||||
|
listenAddress: '监听地址',
|
||||||
|
addressHelper: '指定 SSH 服务监听的 IP 地址,默认为 0.0.0.0,即所有的网络接口都会被监听。',
|
||||||
|
permitRootLogin: 'root 用户',
|
||||||
|
rootSettingHelper: 'root 用户 SSH 登录方式,默认所有 SSH 登录。',
|
||||||
|
rootHelper1: '允许 SSH 登录',
|
||||||
|
rootHelper2: '禁止 SSH 登录',
|
||||||
|
rootHelper3: '仅允许密钥登录',
|
||||||
|
rootHelper4: '仅允许带密码的密钥登录',
|
||||||
|
passwordAuthentication: '密码认证',
|
||||||
|
pwdAuthHelper: '是否启用密码认证,默认启用。',
|
||||||
|
pubkeyAuthentication: '密钥认证',
|
||||||
|
key: '密钥',
|
||||||
|
pubkey: '密钥信息',
|
||||||
|
encryptionMode: '加密方式',
|
||||||
|
generate: '生成密钥',
|
||||||
|
reGenerate: '重新生成密钥',
|
||||||
|
keyAuthHelper: '是否启用密钥认证,默认启用。',
|
||||||
|
useDNS: '反向解析',
|
||||||
|
dnsHelper: '控制 SSH 服务器是否启用 DNS 解析功能,从而验证连接方的身份。',
|
||||||
|
},
|
||||||
setting: {
|
setting: {
|
||||||
all: '全部',
|
all: '全部',
|
||||||
panel: '面板',
|
panel: '面板',
|
||||||
|
@ -50,6 +50,16 @@ const hostRouter = {
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hosts/ssh',
|
||||||
|
name: 'SSH',
|
||||||
|
component: () => import('@/views/host/ssh/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'menu.ssh',
|
||||||
|
keepAlive: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/hosts/firewall/port',
|
path: '/hosts/firewall/port',
|
||||||
name: 'FirewallPort',
|
name: 'FirewallPort',
|
||||||
|
230
frontend/src/views/host/ssh/index.vue
Normal file
230
frontend/src/views/host/ssh/index.vue
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
<template>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<RouterButton
|
||||||
|
:buttons="[
|
||||||
|
{
|
||||||
|
label: i18n.global.t('menu.ssh'),
|
||||||
|
path: '/hosts/ssh',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<LayoutContent style="margin-top: 20px" :title="$t('menu.ssh')" :divider="true">
|
||||||
|
<template #main>
|
||||||
|
<el-radio-group v-model="confShowType" @change="changeMode">
|
||||||
|
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
|
||||||
|
<el-radio-button label="all">{{ $t('database.allConf') }}</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-row style="margin-top: 20px" v-if="confShowType === 'base'">
|
||||||
|
<el-col :span="1"><br /></el-col>
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-form :model="form" label-position="left" ref="formRef" label-width="120px">
|
||||||
|
<el-form-item :label="$t('ssh.port')" prop="port" :rules="Rules.port">
|
||||||
|
<el-input v-model.number="form.port">
|
||||||
|
<template #append>
|
||||||
|
<el-button icon="Collection" @click="onSave(formRef, 'Port', form.port + '')">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('ssh.portHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
|
||||||
|
<el-input v-model="form.listenAddress">
|
||||||
|
<template #append>
|
||||||
|
<el-button
|
||||||
|
icon="Collection"
|
||||||
|
@click="onSave(formRef, 'ListenAddress', form.listenAddress)"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('ssh.addressHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLogin">
|
||||||
|
<el-select
|
||||||
|
v-model="form.permitRootLogin"
|
||||||
|
@change="onSave(formRef, 'PermitRootLogin', form.permitRootLogin)"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option :label="$t('ssh.rootHelper1')" value="yes" />
|
||||||
|
<el-option :label="$t('ssh.rootHelper2')" value="no" />
|
||||||
|
<el-option :label="$t('ssh.rootHelper3')" value="without-password" />
|
||||||
|
<el-option :label="$t('ssh.rootHelper4')" value="forced-commands-only" />
|
||||||
|
</el-select>
|
||||||
|
<span class="input-help">{{ $t('ssh.rootSettingHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication">
|
||||||
|
<el-switch
|
||||||
|
active-value="yes"
|
||||||
|
inactive-value="no"
|
||||||
|
@change="onSave(formRef, 'PasswordAuthentication', form.passwordAuthentication)"
|
||||||
|
v-model="form.passwordAuthentication"
|
||||||
|
></el-switch>
|
||||||
|
<span class="input-help">{{ $t('ssh.pwdAuthHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.pubkeyAuthentication')" prop="pubkeyAuthentication">
|
||||||
|
<el-switch
|
||||||
|
active-value="yes"
|
||||||
|
inactive-value="no"
|
||||||
|
@change="onSave(formRef, 'PubkeyAuthentication', form.pubkeyAuthentication)"
|
||||||
|
v-model="form.pubkeyAuthentication"
|
||||||
|
></el-switch>
|
||||||
|
<span class="input-help">{{ $t('ssh.keyAuthHelper') }}</span>
|
||||||
|
<el-button link @click="onOpenDrawer" type="primary">
|
||||||
|
{{ $t('ssh.pubkey') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.useDNS')" prop="useDNS">
|
||||||
|
<el-switch
|
||||||
|
active-value="yes"
|
||||||
|
inactive-value="no"
|
||||||
|
@change="onSave(formRef, 'UseDNS', form.useDNS)"
|
||||||
|
v-model="form.useDNS"
|
||||||
|
></el-switch>
|
||||||
|
<span class="input-help">{{ $t('ssh.dnsHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div v-if="confShowType === 'all'">
|
||||||
|
<codemirror
|
||||||
|
:autofocus="true"
|
||||||
|
placeholder="# The SSH configuration file does not exist or is empty (/etc/ssh/sshd_config)"
|
||||||
|
:indent-with-tab="true"
|
||||||
|
:tabSize="4"
|
||||||
|
style="margin-top: 10px; height: calc(100vh - 330px)"
|
||||||
|
:lineWrapping="true"
|
||||||
|
:matchBrackets="true"
|
||||||
|
theme="cobalt"
|
||||||
|
:styleActiveLine="true"
|
||||||
|
:extensions="extensions"
|
||||||
|
v-model="sshConf"
|
||||||
|
/>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSaveFile" style="margin-top: 5px">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutContent>
|
||||||
|
|
||||||
|
<PubKey ref="pubKeyRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { Codemirror } from 'vue-codemirror';
|
||||||
|
import LayoutContent from '@/layout/layout-content.vue';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
|
import PubKey from '@/views/host/ssh/pubkey/index.vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { getSSHInfo, updateSSH } from '@/api/modules/host';
|
||||||
|
import { LoadFile, SaveFileContent } from '@/api/modules/files';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const formRef = ref();
|
||||||
|
const extensions = [javascript(), oneDark];
|
||||||
|
const confShowType = ref('base');
|
||||||
|
const pubKeyRef = ref();
|
||||||
|
|
||||||
|
const sshConf = ref();
|
||||||
|
const form = reactive({
|
||||||
|
port: 22,
|
||||||
|
listenAddress: '',
|
||||||
|
passwordAuthentication: 'yes',
|
||||||
|
pubkeyAuthentication: 'yes',
|
||||||
|
encryptionMode: '',
|
||||||
|
primaryKey: '',
|
||||||
|
permitRootLogin: 'yes',
|
||||||
|
useDNS: 'no',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSaveFile = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await SaveFileContent({ path: '/Users/slooop/Downloads/sshd_config', content: sshConf.value })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenDrawer = () => {
|
||||||
|
pubKeyRef.value.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined, key: string, value: string) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
let itemKey = key.replace(key[0], key[0].toLowerCase());
|
||||||
|
const result = await formEl.validateField(itemKey, callback);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.' + itemKey), value]),
|
||||||
|
i18n.global.t('ssh.sshChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await updateSSH(key, value)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
function callback(error: any) {
|
||||||
|
if (error) {
|
||||||
|
return error.message;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSSHConf = async () => {
|
||||||
|
const res = await LoadFile({ path: '/Users/slooop/Downloads/sshd_config' });
|
||||||
|
sshConf.value = res.data || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeMode = async () => {
|
||||||
|
if (confShowType.value === 'all') {
|
||||||
|
loadSSHConf();
|
||||||
|
} else {
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
const res = await getSSHInfo();
|
||||||
|
form.port = Number(res.data.port);
|
||||||
|
form.listenAddress = res.data.listenAddress;
|
||||||
|
form.passwordAuthentication = res.data.passwordAuthentication;
|
||||||
|
form.pubkeyAuthentication = res.data.pubkeyAuthentication;
|
||||||
|
form.permitRootLogin = res.data.permitRootLogin;
|
||||||
|
form.useDNS = res.data.useDNS;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
</script>
|
88
frontend/src/views/host/ssh/pubkey/index.vue
Normal file
88
frontend/src/views/host/ssh/pubkey/index.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
size="50%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('ssh.pubkey')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('ssh.encryptionMode')" prop="encryptionMode">
|
||||||
|
<el-select v-model="form.encryptionMode">
|
||||||
|
<el-option label="ED25519" value="ed25519" />
|
||||||
|
<el-option label="ECDSA" value="ecdsa" />
|
||||||
|
<el-option label="RSA" value="rsa" />
|
||||||
|
<el-option label="DSA" value="dsa" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-button link @click="onDownload" type="primary" class="margintop">
|
||||||
|
{{ form.primaryKey ? $t('ssh.reGenerate') : $t('ssh.generate') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('ssh.key')" prop="primaryKey" v-if="form.encryptionMode">
|
||||||
|
<el-input
|
||||||
|
v-model="form.primaryKey"
|
||||||
|
:autosize="{ minRows: 5, maxRows: 15 }"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
<div v-if="form.primaryKey">
|
||||||
|
<el-button link type="primary" icon="CopyDocument" class="margintop" @click="loadSSLs">
|
||||||
|
{{ $t('file.copy') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="primary" icon="Download" class="margintop" @click="onDownload">
|
||||||
|
{{ $t('commons.button.download') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
encryptionMode: '',
|
||||||
|
primaryKey: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const acceptParams = async (): Promise<void> => {
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const loadSSLs = async () => {};
|
||||||
|
|
||||||
|
const onDownload = async () => {};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('search');
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.margintop {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user