mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-08 01:20:07 +08:00
feat: sshd 支持重启、停止等操作,增加状态条
This commit is contained in:
parent
73d93d6104
commit
2975cf6d5a
@ -23,6 +23,32 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, info)
|
helper.SuccessWithData(c, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags SSH
|
||||||
|
// @Summary Operate ssh
|
||||||
|
// @Description 修改 SSH 服务状态
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.Operate true "request"
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /host/ssh/operate [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
|
||||||
|
func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||||
|
var req dto.Operate
|
||||||
|
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.OperateSSH(req.Operation); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags SSH
|
// @Tags SSH
|
||||||
// @Summary Update host ssh setting
|
// @Summary Update host ssh setting
|
||||||
// @Description 更新 SSH 配置
|
// @Description 更新 SSH 配置
|
||||||
|
@ -23,6 +23,10 @@ type OperateByID struct {
|
|||||||
ID uint `json:"id" validate:"required"`
|
ID uint `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Operate struct {
|
||||||
|
Operation string `json:"operation" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type BatchDeleteReq struct {
|
type BatchDeleteReq struct {
|
||||||
Ids []uint `json:"ids" validate:"required"`
|
Ids []uint `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,13 @@ package dto
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type SSHInfo struct {
|
type SSHInfo struct {
|
||||||
Port string `json:"port" validate:"required,number,max=65535,min=1"`
|
Status string `json:"status"`
|
||||||
|
Port string `json:"port"`
|
||||||
ListenAddress string `json:"listenAddress"`
|
ListenAddress string `json:"listenAddress"`
|
||||||
PasswordAuthentication string `json:"passwordAuthentication" validate:"required,oneof=yes no"`
|
PasswordAuthentication string `json:"passwordAuthentication"`
|
||||||
PubkeyAuthentication string `json:"pubkeyAuthentication" validate:"required,oneof=yes no"`
|
PubkeyAuthentication string `json:"pubkeyAuthentication"`
|
||||||
PermitRootLogin string `json:"permitRootLogin" validate:"required,oneof=yes no without-password forced-commands-only"`
|
PermitRootLogin string `json:"permitRootLogin"`
|
||||||
UseDNS string `json:"useDNS" validate:"required,oneof=yes no"`
|
UseDNS string `json:"useDNS"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GenerateSSH struct {
|
type GenerateSSH struct {
|
||||||
|
@ -24,6 +24,7 @@ type SSHService struct{}
|
|||||||
|
|
||||||
type ISSHService interface {
|
type ISSHService interface {
|
||||||
GetSSHInfo() (*dto.SSHInfo, error)
|
GetSSHInfo() (*dto.SSHInfo, error)
|
||||||
|
OperateSSH(operation string) error
|
||||||
UpdateByFile(value string) error
|
UpdateByFile(value string) error
|
||||||
Update(key, value string) error
|
Update(key, value string) error
|
||||||
GenerateSSH(req dto.GenerateSSH) error
|
GenerateSSH(req dto.GenerateSSH) error
|
||||||
@ -37,6 +38,7 @@ func NewISSHService() ISSHService {
|
|||||||
|
|
||||||
func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||||
data := dto.SSHInfo{
|
data := dto.SSHInfo{
|
||||||
|
Status: constant.StatusDisable,
|
||||||
Port: "22",
|
Port: "22",
|
||||||
ListenAddress: "0.0.0.0",
|
ListenAddress: "0.0.0.0",
|
||||||
PasswordAuthentication: "yes",
|
PasswordAuthentication: "yes",
|
||||||
@ -44,6 +46,25 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
|||||||
PermitRootLogin: "yes",
|
PermitRootLogin: "yes",
|
||||||
UseDNS: "yes",
|
UseDNS: "yes",
|
||||||
}
|
}
|
||||||
|
sudo := ""
|
||||||
|
hasSudo := cmd.HasNoPasswordSudo()
|
||||||
|
if hasSudo {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
stdout, err := cmd.Execf("%s systemctl status sshd", sudo)
|
||||||
|
if err != nil {
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
stdLines := strings.Split(stdout, "\n")
|
||||||
|
for _, stdline := range stdLines {
|
||||||
|
if strings.Contains(stdline, "active (running)") {
|
||||||
|
data.Status = constant.StatusEnable
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.Status == constant.StatusDisable {
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
sshConf, err := os.ReadFile(sshPath)
|
sshConf, err := os.ReadFile(sshPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &data, err
|
return &data, err
|
||||||
@ -72,6 +93,22 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
|||||||
return &data, err
|
return &data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) OperateSSH(operation string) error {
|
||||||
|
if operation == "start" || operation == "stop" || operation == "restart" {
|
||||||
|
sudo := ""
|
||||||
|
hasSudo := cmd.HasNoPasswordSudo()
|
||||||
|
if hasSudo {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
stdout, err := cmd.Execf("%s systemctl %s sshd", sudo, operation)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s sshd failed, stdout: %s, err: %v", operation, stdout, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not support such operation: %s", operation)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *SSHService) Update(key, value string) error {
|
func (u *SSHService) Update(key, value string) error {
|
||||||
sshConf, err := os.ReadFile(sshPath)
|
sshConf, err := os.ReadFile(sshPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -96,7 +133,7 @@ func (u *SSHService) Update(key, value string) error {
|
|||||||
sudo = "sudo"
|
sudo = "sudo"
|
||||||
}
|
}
|
||||||
if key == "Port" {
|
if key == "Port" {
|
||||||
stdout, _ := cmd.Exec("getenforce")
|
stdout, _ := cmd.Execf("%s getenforce", sudo)
|
||||||
if stdout == "Enforcing\n" {
|
if stdout == "Enforcing\n" {
|
||||||
_, _ = cmd.Execf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, value)
|
_, _ = cmd.Execf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, value)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
|
hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret)
|
||||||
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
||||||
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
||||||
|
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
||||||
|
|
||||||
hostRouter.GET("/command", baseApi.ListCommand)
|
hostRouter.GET("/command", baseApi.ListCommand)
|
||||||
hostRouter.POST("/command", baseApi.CreateCommand)
|
hostRouter.POST("/command", baseApi.CreateCommand)
|
||||||
|
@ -105,6 +105,7 @@ export namespace Host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SSHInfo {
|
export interface SSHInfo {
|
||||||
|
status: string;
|
||||||
port: string;
|
port: string;
|
||||||
listenAddress: string;
|
listenAddress: string;
|
||||||
passwordAuthentication: string;
|
passwordAuthentication: string;
|
||||||
|
@ -101,6 +101,9 @@ export const batchOperateRule = (params: Host.BatchRule) => {
|
|||||||
export const getSSHInfo = () => {
|
export const getSSHInfo = () => {
|
||||||
return http.post<Host.SSHInfo>(`/hosts/ssh/search`);
|
return http.post<Host.SSHInfo>(`/hosts/ssh/search`);
|
||||||
};
|
};
|
||||||
|
export const operateSSH = (operation: string) => {
|
||||||
|
return http.post(`/hosts/ssh/operate`, { operation: operation });
|
||||||
|
};
|
||||||
export const updateSSH = (key: string, value: string) => {
|
export const updateSSH = (key: string, value: string) => {
|
||||||
return http.post(`/hosts/ssh/update`, { key: key, value: value });
|
return http.post(`/hosts/ssh/update`, { key: key, value: value });
|
||||||
};
|
};
|
||||||
|
@ -18,16 +18,17 @@
|
|||||||
|
|
||||||
<script setup lang="ts" name="404">
|
<script setup lang="ts" name="404">
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const onCopy = () => {
|
const onCopy = async () => {
|
||||||
let input = document.createElement('input');
|
try {
|
||||||
input.value = '1pctl user-info';
|
await toClipboard('1pctl user-info');
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand('Copy');
|
|
||||||
document.body.removeChild(input);
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
} catch (e) {
|
||||||
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -816,8 +816,9 @@ const message = {
|
|||||||
searchHelper: 'Support wildcards such as *',
|
searchHelper: 'Support wildcards such as *',
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
|
sshOperate: 'Operation [{0}] on the SSH service is performed. Do you want to continue?',
|
||||||
sshChange: 'SSH Setting',
|
sshChange: 'SSH Setting',
|
||||||
sshChangeHelper: 'Are you sure to change the SSH {0} configuration to {1}?',
|
sshChangeHelper: 'This action changed {0} to [{1}]. Do you want to continue?',
|
||||||
sshFileChangeHelper:
|
sshFileChangeHelper:
|
||||||
'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?',
|
'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?',
|
||||||
port: 'Port',
|
port: 'Port',
|
||||||
@ -837,7 +838,7 @@ const message = {
|
|||||||
key: 'Key',
|
key: 'Key',
|
||||||
pubkey: 'Key info',
|
pubkey: 'Key info',
|
||||||
encryptionMode: 'Encryption mode',
|
encryptionMode: 'Encryption mode',
|
||||||
passwordHelper: 'Please enter a 6-10 digit encryption password',
|
passwordHelper: 'Can contain 6 to 10 digits and English cases',
|
||||||
generate: 'Generate key',
|
generate: 'Generate key',
|
||||||
reGenerate: 'Regenerate key',
|
reGenerate: 'Regenerate key',
|
||||||
keyAuthHelper: 'Whether to enable key authentication. This parameter is enabled by default.',
|
keyAuthHelper: 'Whether to enable key authentication. This parameter is enabled by default.',
|
||||||
|
@ -821,8 +821,9 @@ const message = {
|
|||||||
searchHelper: '支持 * 等通配符',
|
searchHelper: '支持 * 等通配符',
|
||||||
},
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
|
sshOperate: '对 SSH 服务进行 [{0}] 操作,是否继续?',
|
||||||
sshChange: 'SSH 配置修改',
|
sshChange: 'SSH 配置修改',
|
||||||
sshChangeHelper: '确认将 SSH {0} 配置修改为 {1} 吗?',
|
sshChangeHelper: '此操作将 {0} 修改为 [{1}] ,是否继续?',
|
||||||
sshFileChangeHelper: '直接修改配置文件可能会导致服务不可用,请谨慎操作,是否继续?',
|
sshFileChangeHelper: '直接修改配置文件可能会导致服务不可用,请谨慎操作,是否继续?',
|
||||||
port: '连接端口',
|
port: '连接端口',
|
||||||
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
||||||
@ -833,14 +834,14 @@ const message = {
|
|||||||
rootHelper1: '允许 SSH 登录',
|
rootHelper1: '允许 SSH 登录',
|
||||||
rootHelper2: '禁止 SSH 登录',
|
rootHelper2: '禁止 SSH 登录',
|
||||||
rootHelper3: '仅允许密钥登录',
|
rootHelper3: '仅允许密钥登录',
|
||||||
rootHelper4: '仅允许执行预先定义的命令,不能进行其他操作。',
|
rootHelper4: '仅允许执行预先定义的命令,不能进行其他操作',
|
||||||
passwordAuthentication: '密码认证',
|
passwordAuthentication: '密码认证',
|
||||||
pwdAuthHelper: '是否启用密码认证,默认启用。',
|
pwdAuthHelper: '是否启用密码认证,默认启用。',
|
||||||
pubkeyAuthentication: '密钥认证',
|
pubkeyAuthentication: '密钥认证',
|
||||||
key: '密钥',
|
key: '密钥',
|
||||||
pubkey: '密钥信息',
|
pubkey: '密钥信息',
|
||||||
encryptionMode: '加密方式',
|
encryptionMode: '加密方式',
|
||||||
passwordHelper: '请输入 6-10 位加密密码',
|
passwordHelper: '支持大小写英文、数字,长度6-10',
|
||||||
generate: '生成密钥',
|
generate: '生成密钥',
|
||||||
reGenerate: '重新生成密钥',
|
reGenerate: '重新生成密钥',
|
||||||
keyAuthHelper: '是否启用密钥认证,默认启用。',
|
keyAuthHelper: '是否启用密钥认证,默认启用。',
|
||||||
|
@ -50,6 +50,25 @@ const hostRouter = {
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hosts/firewall/port',
|
||||||
|
name: 'FirewallPort',
|
||||||
|
component: () => import('@/views/host/firewall/port/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'menu.firewall',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/hosts/firewall/ip',
|
||||||
|
name: 'FirewallIP',
|
||||||
|
component: () => import('@/views/host/firewall/ip/index.vue'),
|
||||||
|
hidden: true,
|
||||||
|
meta: {
|
||||||
|
activeMenu: '/hosts/firewall/port',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/hosts/ssh/ssh',
|
path: '/hosts/ssh/ssh',
|
||||||
name: 'SSH',
|
name: 'SSH',
|
||||||
@ -71,25 +90,6 @@ const hostRouter = {
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/hosts/firewall/port',
|
|
||||||
name: 'FirewallPort',
|
|
||||||
component: () => import('@/views/host/firewall/port/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: 'menu.firewall',
|
|
||||||
requiresAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/hosts/firewall/ip',
|
|
||||||
name: 'FirewallIP',
|
|
||||||
component: () => import('@/views/host/firewall/ip/index.vue'),
|
|
||||||
hidden: true,
|
|
||||||
meta: {
|
|
||||||
activeMenu: '/hosts/firewall/port',
|
|
||||||
requiresAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,11 +73,7 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div style="cursor: pointer; float: left">
|
<div style="cursor: pointer; float: left">
|
||||||
<el-icon
|
<el-icon style="margin-left: 5px; margin-top: 3px" :size="16" @click="onCopy(row)">
|
||||||
style="margin-left: 5px; margin-top: 3px"
|
|
||||||
:size="16"
|
|
||||||
@click="onCopyPassword(row)"
|
|
||||||
>
|
|
||||||
<DocumentCopy />
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
@ -168,7 +164,9 @@ import { Database } from '@/api/interface/database';
|
|||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { GetAppPort } from '@/api/modules/app';
|
import { GetAppPort } from '@/api/modules/app';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const maskShow = ref(true);
|
const maskShow = ref(true);
|
||||||
@ -290,14 +288,13 @@ const checkExist = (data: App.CheckInstalled) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCopyPassword = (row: Database.MysqlDBInfo) => {
|
const onCopy = async (row: any) => {
|
||||||
let input = document.createElement('input');
|
try {
|
||||||
input.value = row.password;
|
await toClipboard(row.password);
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand('Copy');
|
|
||||||
document.body.removeChild(input);
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
} catch (e) {
|
||||||
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDelete = async (row: Database.MysqlDBInfo) => {
|
const onDelete = async (row: Database.MysqlDBInfo) => {
|
||||||
|
@ -9,21 +9,21 @@
|
|||||||
<el-form-item :label="$t('database.rootPassword')" :rules="Rules.requiredInput" prop="password">
|
<el-form-item :label="$t('database.rootPassword')" :rules="Rules.requiredInput" prop="password">
|
||||||
<el-input type="password" show-password clearable v-model="form.password">
|
<el-input type="password" show-password clearable v-model="form.password">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button @click="copy(form.password)" icon="DocumentCopy"></el-button>
|
<el-button @click="onCopy(form.password)" icon="DocumentCopy"></el-button>
|
||||||
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('database.serviceName')" prop="serviceName">
|
<el-form-item :label="$t('database.serviceName')" prop="serviceName">
|
||||||
<el-tag>{{ form.serviceName }}</el-tag>
|
<el-tag>{{ form.serviceName }}</el-tag>
|
||||||
<el-button @click="copy(form.serviceName)" icon="DocumentCopy" link></el-button>
|
<el-button @click="onCopy(form.serviceName)" icon="DocumentCopy" link></el-button>
|
||||||
<span class="input-help">{{ $t('database.serviceNameHelper') }}</span>
|
<span class="input-help">{{ $t('database.serviceNameHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('database.containerConn')">
|
<el-form-item :label="$t('database.containerConn')">
|
||||||
<el-tag>
|
<el-tag>
|
||||||
{{ form.serviceName + ':3306' }}
|
{{ form.serviceName + ':3306' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-button @click="copy(form.serviceName + ':3306')" icon="DocumentCopy" link></el-button>
|
<el-button @click="onCopy(form.serviceName + ':3306')" icon="DocumentCopy" link></el-button>
|
||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
{{ $t('database.containerConnHelper') }}
|
{{ $t('database.containerConnHelper') }}
|
||||||
</span>
|
</span>
|
||||||
@ -60,9 +60,11 @@ import { updateMysqlPassword } from '@/api/modules/database';
|
|||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import { GetAppConnInfo } from '@/api/modules/app';
|
import { GetAppConnInfo } from '@/api/modules/app';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { getRandomStr } from '@/utils/util';
|
import { getRandomStr } from '@/utils/util';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
@ -88,14 +90,13 @@ const random = async () => {
|
|||||||
form.value.password = getRandomStr(16);
|
form.value.password = getRandomStr(16);
|
||||||
};
|
};
|
||||||
|
|
||||||
const copy = async (value: string) => {
|
const onCopy = async (value: string) => {
|
||||||
let input = document.createElement('input');
|
try {
|
||||||
input.value = value;
|
await toClipboard(value);
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand('Copy');
|
|
||||||
document.body.removeChild(input);
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
} catch (e) {
|
||||||
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
@ -9,21 +9,21 @@
|
|||||||
<el-form-item :label="$t('database.requirepass')" :rules="Rules.requiredInput" prop="password">
|
<el-form-item :label="$t('database.requirepass')" :rules="Rules.requiredInput" prop="password">
|
||||||
<el-input type="password" show-password clearable v-model="form.password">
|
<el-input type="password" show-password clearable v-model="form.password">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button @click="copy(form.password)" icon="DocumentCopy"></el-button>
|
<el-button @click="onCopy(form.password)" icon="DocumentCopy"></el-button>
|
||||||
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('database.serviceName')" prop="serviceName">
|
<el-form-item :label="$t('database.serviceName')" prop="serviceName">
|
||||||
<el-tag>{{ form.serviceName }}</el-tag>
|
<el-tag>{{ form.serviceName }}</el-tag>
|
||||||
<el-button @click="copy(form.serviceName)" icon="DocumentCopy" link></el-button>
|
<el-button @click="onCopy(form.serviceName)" icon="DocumentCopy" link></el-button>
|
||||||
<span class="input-help">{{ $t('database.serviceNameHelper') }}</span>
|
<span class="input-help">{{ $t('database.serviceNameHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('database.containerConn')">
|
<el-form-item :label="$t('database.containerConn')">
|
||||||
<el-tag>
|
<el-tag>
|
||||||
{{ form.serviceName + ':6379' }}
|
{{ form.serviceName + ':6379' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-button @click="copy(form.serviceName + ':6379')" icon="DocumentCopy" link></el-button>
|
<el-button @click="onCopy(form.serviceName + ':6379')" icon="DocumentCopy" link></el-button>
|
||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
{{ $t('database.containerConnHelper') }}
|
{{ $t('database.containerConnHelper') }}
|
||||||
</span>
|
</span>
|
||||||
@ -59,10 +59,12 @@ import { ElForm } from 'element-plus';
|
|||||||
import { changeRedisPassword } from '@/api/modules/database';
|
import { changeRedisPassword } from '@/api/modules/database';
|
||||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||||
import { GetAppConnInfo } from '@/api/modules/app';
|
import { GetAppConnInfo } from '@/api/modules/app';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { getRandomStr } from '@/utils/util';
|
import { getRandomStr } from '@/utils/util';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
@ -93,14 +95,13 @@ const random = async () => {
|
|||||||
form.value.password = getRandomStr(16);
|
form.value.password = getRandomStr(16);
|
||||||
};
|
};
|
||||||
|
|
||||||
const copy = async (value: string) => {
|
const onCopy = async (value: string) => {
|
||||||
let input = document.createElement('input');
|
try {
|
||||||
input.value = value;
|
await toClipboard(value);
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand('Copy');
|
|
||||||
document.body.removeChild(input);
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
} catch (e) {
|
||||||
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPassword = async () => {
|
const loadPassword = async () => {
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
||||||
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
|
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button type="success" plain @click="onSearch('Success')" style="margin-left: 25px">
|
<el-button type="success" plain style="margin-left: 25px">
|
||||||
{{ $t('commons.status.success') }}: {{ successfulCount }}
|
{{ $t('commons.status.success') }}: {{ successfulCount }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" plain @click="onSearch('Failed')" style="margin-left: 5px">
|
<el-button type="danger" plain style="margin-left: 5px">
|
||||||
{{ $t('commons.status.failed') }}: {{ faliedCount }}
|
{{ $t('commons.status.failed') }}: {{ faliedCount }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -120,11 +120,6 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch = (status: string) => {
|
|
||||||
searchStatus.value = status;
|
|
||||||
search();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
|
103
frontend/src/views/host/ssh/ssh/address/index.vue
Normal file
103
frontend/src/views/host/ssh/ssh/address/index.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
size="30%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('ssh.listenAddress')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('ssh.listenAddress')"
|
||||||
|
prop="listenAddress"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input clearable v-model="form.listenAddress" />
|
||||||
|
</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>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { updateSSH } from '@/api/modules/host';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
listenAddress: '0.0.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.listenAddress = params.address;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.listenAddress'), form.listenAddress]),
|
||||||
|
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('ListenAddress', form.listenAddress)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
emit('search');
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
emit('search');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -2,6 +2,38 @@
|
|||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<FireRouter />
|
<FireRouter />
|
||||||
|
|
||||||
|
<div class="a-card" style="margin-top: 20px">
|
||||||
|
<el-card>
|
||||||
|
<div>
|
||||||
|
<el-tag style="float: left" effect="dark" type="success">SSH</el-tag>
|
||||||
|
<el-tag round class="status-content" v-if="form.status === 'Enable'" type="success">
|
||||||
|
{{ $t('commons.status.running') }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag round class="status-content" v-if="form.status === 'Disable'" type="info">
|
||||||
|
{{ $t('commons.status.stopped') }}
|
||||||
|
</el-tag>
|
||||||
|
<span v-if="form.status === 'Enable'" class="buttons">
|
||||||
|
<el-button type="primary" @click="onOperate('stop')" link>
|
||||||
|
{{ $t('commons.button.stop') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button type="primary" @click="onOperate('restart')" link>
|
||||||
|
{{ $t('container.restart') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
<span v-if="form.status === 'Disable'" class="buttons">
|
||||||
|
<el-button type="primary" @click="onOperate('start')" link>
|
||||||
|
{{ $t('commons.button.start') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button type="primary" @click="onOperate('restart')" link>
|
||||||
|
{{ $t('container.restart') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<LayoutContent style="margin-top: 20px" :title="$t('menu.ssh')" :divider="true">
|
<LayoutContent style="margin-top: 20px" :title="$t('menu.ssh')" :divider="true">
|
||||||
<template #main>
|
<template #main>
|
||||||
<el-radio-group v-model="confShowType" @change="changeMode">
|
<el-radio-group v-model="confShowType" @change="changeMode">
|
||||||
@ -12,40 +44,34 @@
|
|||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-form :model="form" label-position="left" ref="formRef" label-width="120px">
|
<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-form-item :label="$t('ssh.port')" prop="port">
|
||||||
<el-input v-model.number="form.port">
|
<el-input disabled v-model.number="form.port">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button icon="Collection" @click="onSave(formRef, 'Port', form.port + '')">
|
<el-button @click="onChangePort" icon="Setting">
|
||||||
{{ $t('commons.button.save') }}
|
{{ $t('commons.button.set') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<span class="input-help">{{ $t('ssh.portHelper') }}</span>
|
<span class="input-help">{{ $t('ssh.portHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
|
<el-form-item :label="$t('ssh.listenAddress')" prop="listenAddress">
|
||||||
<el-input v-model="form.listenAddress">
|
<el-input disabled v-model="form.listenAddress">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button
|
<el-button @click="onChangeAddress" icon="Setting">
|
||||||
icon="Collection"
|
{{ $t('commons.button.set') }}
|
||||||
@click="onSave(formRef, 'ListenAddress', form.listenAddress)"
|
|
||||||
>
|
|
||||||
{{ $t('commons.button.save') }}
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<span class="input-help">{{ $t('ssh.addressHelper') }}</span>
|
<span class="input-help">{{ $t('ssh.addressHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLogin">
|
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLoginItem">
|
||||||
<el-select
|
<el-input disabled v-model="form.permitRootLoginItem">
|
||||||
v-model="form.permitRootLogin"
|
<template #append>
|
||||||
@change="onSave(formRef, 'PermitRootLogin', form.permitRootLogin)"
|
<el-button @click="onChangeRoot" icon="Setting">
|
||||||
style="width: 100%"
|
{{ $t('commons.button.set') }}
|
||||||
>
|
</el-button>
|
||||||
<el-option :label="$t('ssh.rootHelper1')" value="yes" />
|
</template>
|
||||||
<el-option :label="$t('ssh.rootHelper2')" value="no" />
|
</el-input>
|
||||||
<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>
|
<span class="input-help">{{ $t('ssh.rootSettingHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication">
|
<el-form-item :label="$t('ssh.passwordAuthentication')" prop="passwordAuthentication">
|
||||||
@ -103,7 +129,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
|
|
||||||
<PubKey ref="pubKeyRef" />
|
<PubKey ref="pubKeyRef" @search="search" />
|
||||||
|
<Port ref="portRef" @search="search" />
|
||||||
|
<Address ref="addressRef" @search="search" />
|
||||||
|
<Root ref="rootsRef" @search="search" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -115,11 +144,13 @@ import LayoutContent from '@/layout/layout-content.vue';
|
|||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
import PubKey from '@/views/host/ssh/ssh/pubkey/index.vue';
|
import PubKey from '@/views/host/ssh/ssh/pubkey/index.vue';
|
||||||
|
import Root from '@/views/host/ssh/ssh/root/index.vue';
|
||||||
|
import Port from '@/views/host/ssh/ssh/port/index.vue';
|
||||||
|
import Address from '@/views/host/ssh/ssh/address/index.vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { getSSHInfo, updateSSH, updateSSHByfile } from '@/api/modules/host';
|
import { getSSHInfo, operateSSH, updateSSH, updateSSHByfile } from '@/api/modules/host';
|
||||||
import { LoadFile } from '@/api/modules/files';
|
import { LoadFile } from '@/api/modules/files';
|
||||||
import { Rules } from '@/global/form-rules';
|
|
||||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -127,9 +158,13 @@ const formRef = ref();
|
|||||||
const extensions = [javascript(), oneDark];
|
const extensions = [javascript(), oneDark];
|
||||||
const confShowType = ref('base');
|
const confShowType = ref('base');
|
||||||
const pubKeyRef = ref();
|
const pubKeyRef = ref();
|
||||||
|
const portRef = ref();
|
||||||
|
const addressRef = ref();
|
||||||
|
const rootsRef = ref();
|
||||||
|
|
||||||
const sshConf = ref();
|
const sshConf = ref();
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
status: 'enable',
|
||||||
port: 22,
|
port: 22,
|
||||||
listenAddress: '',
|
listenAddress: '',
|
||||||
passwordAuthentication: 'yes',
|
passwordAuthentication: 'yes',
|
||||||
@ -137,6 +172,7 @@ const form = reactive({
|
|||||||
encryptionMode: '',
|
encryptionMode: '',
|
||||||
primaryKey: '',
|
primaryKey: '',
|
||||||
permitRootLogin: 'yes',
|
permitRootLogin: 'yes',
|
||||||
|
permitRootLoginItem: 'yes',
|
||||||
useDNS: 'no',
|
useDNS: 'no',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,6 +198,35 @@ const onOpenDrawer = () => {
|
|||||||
pubKeyRef.value.acceptParams();
|
pubKeyRef.value.acceptParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangePort = () => {
|
||||||
|
portRef.value.acceptParams({ port: form.port });
|
||||||
|
};
|
||||||
|
const onChangeRoot = () => {
|
||||||
|
rootsRef.value.acceptParams({ permitRootLogin: form.permitRootLogin });
|
||||||
|
};
|
||||||
|
const onChangeAddress = () => {
|
||||||
|
addressRef.value.acceptParams({ address: form.listenAddress });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOperate = async (operation: string) => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('ssh.sshOperate', [i18n.global.t('commons.button.' + operation)]), 'SSH', {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await operateSSH(operation)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onSave = async (formEl: FormInstance | undefined, key: string, value: string) => {
|
const onSave = async (formEl: FormInstance | undefined, key: string, value: string) => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
let itemKey = key.replace(key[0], key[0].toLowerCase());
|
let itemKey = key.replace(key[0], key[0].toLowerCase());
|
||||||
@ -208,12 +273,6 @@ const changei18n = (value: string) => {
|
|||||||
return i18n.global.t('commons.button.enable');
|
return i18n.global.t('commons.button.enable');
|
||||||
case 'no':
|
case 'no':
|
||||||
return i18n.global.t('commons.button.disable');
|
return i18n.global.t('commons.button.disable');
|
||||||
case 'without-password':
|
|
||||||
return i18n.global.t('ssh.rootHelper3');
|
|
||||||
case 'forced-commands-only':
|
|
||||||
return i18n.global.t('ssh.rootHelper4');
|
|
||||||
case 'yes':
|
|
||||||
return i18n.global.t('commons.button.enable');
|
|
||||||
default:
|
default:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -234,15 +293,46 @@ const changeMode = async () => {
|
|||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
const res = await getSSHInfo();
|
const res = await getSSHInfo();
|
||||||
|
form.status = res.data.status;
|
||||||
form.port = Number(res.data.port);
|
form.port = Number(res.data.port);
|
||||||
form.listenAddress = res.data.listenAddress;
|
form.listenAddress = res.data.listenAddress;
|
||||||
form.passwordAuthentication = res.data.passwordAuthentication;
|
form.passwordAuthentication = res.data.passwordAuthentication;
|
||||||
form.pubkeyAuthentication = res.data.pubkeyAuthentication;
|
form.pubkeyAuthentication = res.data.pubkeyAuthentication;
|
||||||
form.permitRootLogin = res.data.permitRootLogin;
|
form.permitRootLogin = res.data.permitRootLogin;
|
||||||
|
form.permitRootLoginItem = loadPermitLabel(res.data.permitRootLogin);
|
||||||
form.useDNS = res.data.useDNS;
|
form.useDNS = res.data.useDNS;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadPermitLabel = (value: string) => {
|
||||||
|
switch (value) {
|
||||||
|
case 'yes':
|
||||||
|
return i18n.global.t('ssh.rootHelper1');
|
||||||
|
case 'no':
|
||||||
|
return i18n.global.t('ssh.rootHelper2');
|
||||||
|
case 'without-password':
|
||||||
|
return i18n.global.t('ssh.rootHelper3');
|
||||||
|
case 'forced-commands-only':
|
||||||
|
return i18n.global.t('ssh.rootHelper4');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.a-card {
|
||||||
|
font-size: 17px;
|
||||||
|
.el-card {
|
||||||
|
--el-card-padding: 12px;
|
||||||
|
.buttons {
|
||||||
|
margin-left: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-content {
|
||||||
|
float: left;
|
||||||
|
margin-left: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
99
frontend/src/views/host/ssh/ssh/port/index.vue
Normal file
99
frontend/src/views/host/ssh/ssh/port/index.vue
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
size="30%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('ssh.port')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('ssh.port')" prop="port" :rules="Rules.port">
|
||||||
|
<el-input clearable v-model.number="form.port" />
|
||||||
|
</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>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { updateSSH } from '@/api/modules/host';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
port: 22,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.port = params.port;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.port'), form.port]),
|
||||||
|
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('Port', form.port + '')
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
emit('search');
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
emit('search');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -28,28 +28,19 @@
|
|||||||
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
<el-button style="margin-left: 1px" @click="random" icon="RefreshRight"></el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<el-button link @click="onGenerate(formRef)" type="primary" class="margintop">
|
|
||||||
{{ form.primaryKey ? $t('ssh.reGenerate') : $t('ssh.generate') }}
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('ssh.key')" prop="primaryKey" v-if="form.encryptionMode">
|
<el-form-item :label="$t('ssh.key')" prop="primaryKey" v-if="form.encryptionMode">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="form.primaryKey"
|
v-model="form.primaryKey"
|
||||||
:autosize="{ minRows: 5, maxRows: 15 }"
|
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
<div v-if="form.primaryKey">
|
<div v-if="form.primaryKey">
|
||||||
<el-button
|
<el-button icon="CopyDocument" class="margintop" @click="onCopy(form.primaryKey)">
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
icon="CopyDocument"
|
|
||||||
class="margintop"
|
|
||||||
@click="onCopy(form.primaryKey)"
|
|
||||||
>
|
|
||||||
{{ $t('file.copy') }}
|
{{ $t('file.copy') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button link type="primary" icon="Download" class="margintop" @click="onDownload">
|
<el-button icon="Download" class="margintop" @click="onDownload">
|
||||||
{{ $t('commons.button.download') }}
|
{{ $t('commons.button.download') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -60,6 +51,9 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button @click="onGenerate(formRef)" type="primary">
|
||||||
|
{{ $t('ssh.generate') }}
|
||||||
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
@ -69,10 +63,12 @@
|
|||||||
import { generateSecret, loadSecret } from '@/api/modules/host';
|
import { generateSecret, loadSecret } from '@/api/modules/host';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { dateFormatForName, getRandomStr } from '@/utils/util';
|
import { dateFormatForName, getRandomStr } from '@/utils/util';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
import { FormInstance } from 'element-plus';
|
import { FormInstance } from 'element-plus';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const drawerVisiable = ref();
|
const drawerVisiable = ref();
|
||||||
@ -117,10 +113,10 @@ const onLoadSecret = async () => {
|
|||||||
|
|
||||||
const onCopy = async (str: string) => {
|
const onCopy = async (str: string) => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(str);
|
await toClipboard(str);
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copyfailed'));
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
119
frontend/src/views/host/ssh/ssh/root/index.vue
Normal file
119
frontend/src/views/host/ssh/ssh/root/index.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
size="30%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('ssh.permitRootLogin')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('ssh.permitRootLogin')" prop="permitRootLogin">
|
||||||
|
<el-select v-model="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>
|
||||||
|
</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>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSave(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
import { updateSSH } from '@/api/modules/host';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
permitRootLogin: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
permitRootLogin: 'yes',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.permitRootLogin = params.permitRootLogin;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('ssh.sshChangeHelper', [
|
||||||
|
i18n.global.t('ssh.permitRootLogin'),
|
||||||
|
loadPermitLabel(form.permitRootLogin),
|
||||||
|
]),
|
||||||
|
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('PermitRootLogin', form.permitRootLogin)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
handleClose();
|
||||||
|
emit('search');
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
emit('search');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPermitLabel = (value: string) => {
|
||||||
|
switch (value) {
|
||||||
|
case 'yes':
|
||||||
|
return i18n.global.t('ssh.rootHelper1');
|
||||||
|
case 'no':
|
||||||
|
return i18n.global.t('ssh.rootHelper2');
|
||||||
|
case 'without-password':
|
||||||
|
return i18n.global.t('ssh.rootHelper3');
|
||||||
|
case 'forced-commands-only':
|
||||||
|
return i18n.global.t('ssh.rootHelper4');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -74,8 +74,10 @@ import { bindMFA, getMFA } from '@/api/modules/setting';
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { FormInstance } from 'element-plus';
|
import { FormInstance } from 'element-plus';
|
||||||
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const qrImage = ref();
|
const qrImage = ref();
|
||||||
@ -93,14 +95,13 @@ const acceptParams = (): void => {
|
|||||||
drawerVisiable.value = true;
|
drawerVisiable.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCopy = () => {
|
const onCopy = async () => {
|
||||||
let input = document.createElement('input');
|
try {
|
||||||
input.value = form.secret;
|
await toClipboard(form.secret);
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand('Copy');
|
|
||||||
document.body.removeChild(input);
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
} catch (e) {
|
||||||
|
MsgError(i18n.global.t('commons.msg.copyfailed'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMfaCode = async () => {
|
const loadMfaCode = async () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user