mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-11 07:55:59 +08:00
feat: 配置修改后重启,适配 SELinux 策略
This commit is contained in:
parent
efd545882f
commit
7452cc19e0
11 changed files with 210 additions and 143 deletions
|
@ -50,6 +50,33 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags SSH
|
||||||
|
// @Summary Update host ssh setting by file
|
||||||
|
// @Description 上传文件更新 SSH 配置
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SSHConf true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /host/conffile/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||||
|
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||||
|
var req dto.SSHConf
|
||||||
|
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.UpdateByFile(req.File); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags SSH
|
// @Tags SSH
|
||||||
// @Summary Generate host ssh secret
|
// @Summary Generate host ssh secret
|
||||||
// @Description 生成 ssh 密钥
|
// @Description 生成 ssh 密钥
|
||||||
|
|
|
@ -20,6 +20,9 @@ type GenerateLoad struct {
|
||||||
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
|
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSHConf struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
}
|
||||||
type SearchSSHLog struct {
|
type SearchSSHLog struct {
|
||||||
PageInfo
|
PageInfo
|
||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
|
@ -33,7 +36,8 @@ type SSHLog struct {
|
||||||
}
|
}
|
||||||
type SSHHistory struct {
|
type SSHHistory struct {
|
||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date"`
|
||||||
Belong string `json:"belong"`
|
DateStr string `json:"dateStr"`
|
||||||
|
IsLocal bool `json:"isLocal"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
AuthMode string `json:"authMode"`
|
AuthMode string `json:"authMode"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
|
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +24,7 @@ type SSHService struct{}
|
||||||
|
|
||||||
type ISSHService interface {
|
type ISSHService interface {
|
||||||
GetSSHInfo() (*dto.SSHInfo, error)
|
GetSSHInfo() (*dto.SSHInfo, 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
|
||||||
LoadSSHSecret(mode string) (string, error)
|
LoadSSHSecret(mode string) (string, error)
|
||||||
|
@ -87,6 +90,36 @@ func (u *SSHService) Update(key, value string) error {
|
||||||
if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil {
|
if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
sudo := ""
|
||||||
|
hasSudo := cmd.HasNoPasswordSudo()
|
||||||
|
if hasSudo {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
if key == "Port" {
|
||||||
|
stdout, _ := cmd.Exec("getenforce")
|
||||||
|
if stdout == "Enforcing\n" {
|
||||||
|
_, _ = cmd.Execf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, _ = cmd.Execf("%s systemctl restart sshd", sudo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SSHService) UpdateByFile(value string) error {
|
||||||
|
file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
if _, err = file.WriteString(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sudo := ""
|
||||||
|
hasSudo := cmd.HasNoPasswordSudo()
|
||||||
|
if hasSudo {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
_, _ = cmd.Execf("%s systemctl restart sshd", sudo)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +231,15 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
|
||||||
}
|
}
|
||||||
data.SuccessfulCount = data.TotalCount - data.FailedCount
|
data.SuccessfulCount = data.TotalCount - data.FailedCount
|
||||||
|
|
||||||
|
timeNow := time.Now()
|
||||||
|
nyc, _ := time.LoadLocation(common.LoadTimeZone())
|
||||||
|
for i := 0; i < len(data.Logs); i++ {
|
||||||
|
data.Logs[i].IsLocal = isPrivateIP(net.ParseIP(data.Logs[i].Address))
|
||||||
|
data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc)
|
||||||
|
if data.Logs[i].Date.After(timeNow) {
|
||||||
|
data.Logs[i].Date = data.Logs[i].Date.AddDate(-1, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
sort.Slice(data.Logs, func(i, j int) bool {
|
sort.Slice(data.Logs, func(i, j int) bool {
|
||||||
return data.Logs[i].Date.After(data.Logs[j].Date)
|
return data.Logs[i].Date.After(data.Logs[j].Date)
|
||||||
})
|
})
|
||||||
|
@ -249,27 +291,22 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
|
||||||
|
|
||||||
func loadSuccessDatas(command string) []dto.SSHHistory {
|
func loadSuccessDatas(command string) []dto.SSHHistory {
|
||||||
var datas []dto.SSHHistory
|
var datas []dto.SSHHistory
|
||||||
timeNow := time.Now()
|
|
||||||
stdout2, err := cmd.Exec(command)
|
stdout2, err := cmd.Exec(command)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lines := strings.Split(string(stdout2), "\n")
|
lines := strings.Split(string(stdout2), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
if len(parts) != 14 {
|
if len(parts) < 14 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
historyItem := dto.SSHHistory{
|
historyItem := dto.SSHHistory{
|
||||||
Belong: parts[3],
|
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||||
AuthMode: parts[6],
|
AuthMode: parts[6],
|
||||||
User: parts[8],
|
User: parts[8],
|
||||||
Address: parts[10],
|
Address: parts[10],
|
||||||
Port: parts[12],
|
Port: parts[12],
|
||||||
Status: constant.StatusSuccess,
|
Status: constant.StatusSuccess,
|
||||||
}
|
}
|
||||||
historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2]))
|
|
||||||
if historyItem.Date.After(timeNow) {
|
|
||||||
historyItem.Date = historyItem.Date.AddDate(-1, 0, 0)
|
|
||||||
}
|
|
||||||
datas = append(datas, historyItem)
|
datas = append(datas, historyItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,29 +315,24 @@ func loadSuccessDatas(command string) []dto.SSHHistory {
|
||||||
|
|
||||||
func loadFailedAuthDatas(command string) []dto.SSHHistory {
|
func loadFailedAuthDatas(command string) []dto.SSHHistory {
|
||||||
var datas []dto.SSHHistory
|
var datas []dto.SSHHistory
|
||||||
timeNow := time.Now()
|
|
||||||
stdout2, err := cmd.Exec(command)
|
stdout2, err := cmd.Exec(command)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lines := strings.Split(string(stdout2), "\n")
|
lines := strings.Split(string(stdout2), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
if len(parts) != 15 {
|
if len(parts) < 14 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
historyItem := dto.SSHHistory{
|
historyItem := dto.SSHHistory{
|
||||||
Belong: parts[3],
|
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||||
AuthMode: parts[8],
|
AuthMode: parts[8],
|
||||||
User: parts[10],
|
User: parts[10],
|
||||||
Address: parts[11],
|
Address: parts[11],
|
||||||
Port: parts[13],
|
Port: parts[13],
|
||||||
Status: constant.StatusFailed,
|
Status: constant.StatusFailed,
|
||||||
}
|
}
|
||||||
historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2]))
|
|
||||||
if historyItem.Date.After(timeNow) {
|
|
||||||
historyItem.Date = historyItem.Date.AddDate(-1, 0, 0)
|
|
||||||
}
|
|
||||||
if strings.Contains(line, ": ") {
|
if strings.Contains(line, ": ") {
|
||||||
historyItem.Message = strings.Split(line, ": ")[0]
|
historyItem.Message = strings.Split(line, ": ")[1]
|
||||||
}
|
}
|
||||||
datas = append(datas, historyItem)
|
datas = append(datas, historyItem)
|
||||||
}
|
}
|
||||||
|
@ -310,29 +342,24 @@ func loadFailedAuthDatas(command string) []dto.SSHHistory {
|
||||||
|
|
||||||
func loadFailedSecureDatas(command string) []dto.SSHHistory {
|
func loadFailedSecureDatas(command string) []dto.SSHHistory {
|
||||||
var datas []dto.SSHHistory
|
var datas []dto.SSHHistory
|
||||||
timeNow := time.Now()
|
|
||||||
stdout2, err := cmd.Exec(command)
|
stdout2, err := cmd.Exec(command)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lines := strings.Split(string(stdout2), "\n")
|
lines := strings.Split(string(stdout2), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
if len(parts) != 14 {
|
if len(parts) < 14 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
historyItem := dto.SSHHistory{
|
historyItem := dto.SSHHistory{
|
||||||
Belong: parts[3],
|
DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]),
|
||||||
AuthMode: parts[6],
|
AuthMode: parts[6],
|
||||||
User: parts[8],
|
User: parts[8],
|
||||||
Address: parts[10],
|
Address: parts[10],
|
||||||
Port: parts[12],
|
Port: parts[12],
|
||||||
Status: constant.StatusFailed,
|
Status: constant.StatusFailed,
|
||||||
}
|
}
|
||||||
historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2]))
|
|
||||||
if historyItem.Date.After(timeNow) {
|
|
||||||
historyItem.Date = historyItem.Date.AddDate(-1, 0, 0)
|
|
||||||
}
|
|
||||||
if strings.Contains(line, ": ") {
|
if strings.Contains(line, ": ") {
|
||||||
historyItem.Message = strings.Split(line, ": ")[0]
|
historyItem.Message = strings.Split(line, ": ")[1]
|
||||||
}
|
}
|
||||||
datas = append(datas, historyItem)
|
datas = append(datas, historyItem)
|
||||||
}
|
}
|
||||||
|
@ -346,3 +373,16 @@ func handleGunzip(path string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func isPrivateIP(ip net.IP) bool {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
switch true {
|
||||||
|
case ip4[0] == 10:
|
||||||
|
return true
|
||||||
|
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
|
||||||
|
return true
|
||||||
|
case ip4[0] == 192 && ip4[1] == 168:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCa(t *testing.T) {
|
|
||||||
var (
|
|
||||||
fileList []string
|
|
||||||
datas []history
|
|
||||||
successfulCount int
|
|
||||||
failedCount int
|
|
||||||
)
|
|
||||||
baseDir := "/Users/slooop/Downloads"
|
|
||||||
if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() && strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") {
|
|
||||||
fileList = append(fileList, strings.ReplaceAll(pathItem, ".gz", ""))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
for i := 0; i < len(fileList); i++ {
|
|
||||||
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
|
|
||||||
dataItem := loadDatas2(fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid'", fileList[i]), 14, constant.StatusFailed)
|
|
||||||
failedCount += len(dataItem)
|
|
||||||
datas = append(datas, dataItem...)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
|
|
||||||
dataItem := loadDatas2(fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth'", fileList[i]), 15, constant.StatusFailed)
|
|
||||||
failedCount += len(dataItem)
|
|
||||||
datas = append(datas, dataItem...)
|
|
||||||
}
|
|
||||||
dataItem := loadDatas2(fmt.Sprintf("cat %s | grep Accepted", fileList[i]), 14, constant.StatusSuccess)
|
|
||||||
datas = append(datas, dataItem...)
|
|
||||||
}
|
|
||||||
successfulCount = len(datas) - failedCount
|
|
||||||
fmt.Println(len(datas), successfulCount, failedCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadDatas2(command string, length int, status string) []history {
|
|
||||||
var datas []history
|
|
||||||
stdout2, err := cmd.Exec(command)
|
|
||||||
if err == nil {
|
|
||||||
lines := strings.Split(string(stdout2), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
parts := strings.Fields(line)
|
|
||||||
if len(parts) != length {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
historyItem := history{
|
|
||||||
Belong: parts[3],
|
|
||||||
User: parts[8],
|
|
||||||
AuthMode: parts[6],
|
|
||||||
Address: parts[10],
|
|
||||||
Port: parts[12],
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
dateStr := fmt.Sprintf("%d %s %s %s", time.Now().Year(), parts[0], parts[1], parts[2])
|
|
||||||
historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", dateStr)
|
|
||||||
// if err != nil {
|
|
||||||
// historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", dateStr)
|
|
||||||
// }
|
|
||||||
fmt.Println(dateStr + "===>" + historyItem.Date.Format("2006.01.02 15:04:05"))
|
|
||||||
datas = append(datas, historyItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return datas
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCas(t *testing.T) {
|
|
||||||
ss := "2023 May 9 14:48:28"
|
|
||||||
kk, err := time.Parse("2006 Jan 2 15:04:05", ss)
|
|
||||||
fmt.Println(kk, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type history struct {
|
|
||||||
Date time.Time
|
|
||||||
Belong string
|
|
||||||
User string
|
|
||||||
AuthMode string
|
|
||||||
Address string
|
|
||||||
Port string
|
|
||||||
Status string
|
|
||||||
Message string
|
|
||||||
}
|
|
|
@ -40,6 +40,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
|
||||||
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
hostRouter.POST("/ssh/generate", baseApi.GenerateSSH)
|
||||||
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.GET("/command", baseApi.ListCommand)
|
hostRouter.GET("/command", baseApi.ListCommand)
|
||||||
hostRouter.POST("/command", baseApi.CreateCommand)
|
hostRouter.POST("/command", baseApi.CreateCommand)
|
||||||
|
|
|
@ -129,7 +129,7 @@ export namespace Host {
|
||||||
}
|
}
|
||||||
export interface sshHistory {
|
export interface sshHistory {
|
||||||
date: Date;
|
date: Date;
|
||||||
belong: string;
|
isLocal: boolean;
|
||||||
user: string;
|
user: string;
|
||||||
authMode: string;
|
authMode: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
|
|
@ -104,6 +104,9 @@ export const getSSHInfo = () => {
|
||||||
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 });
|
||||||
};
|
};
|
||||||
|
export const updateSSHByfile = (file: string) => {
|
||||||
|
return http.post(`/hosts/ssh/conffile/update`, { file: file });
|
||||||
|
};
|
||||||
export const generateSecret = (params: Host.SSHGenerate) => {
|
export const generateSecret = (params: Host.SSHGenerate) => {
|
||||||
return http.post(`/hosts/ssh/generate`, params);
|
return http.post(`/hosts/ssh/generate`, params);
|
||||||
};
|
};
|
||||||
|
|
|
@ -815,6 +815,45 @@ const message = {
|
||||||
'The default user of the PHP operating environment: the user group is 1000:1000, it is normal that the users inside and outside the container show inconsistencies',
|
'The default user of the PHP operating environment: the user group is 1000:1000, it is normal that the users inside and outside the container show inconsistencies',
|
||||||
searchHelper: 'Support wildcards such as *',
|
searchHelper: 'Support wildcards such as *',
|
||||||
},
|
},
|
||||||
|
ssh: {
|
||||||
|
sshChange: 'SSH Setting',
|
||||||
|
sshChangeHelper: 'Are you sure to change the SSH {0} configuration to {1}?',
|
||||||
|
sshFileChangeHelper:
|
||||||
|
'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?',
|
||||||
|
port: 'Port',
|
||||||
|
portHelper: 'Specifies the port number monitored by the SSH service. The default port number is 22.',
|
||||||
|
listenAddress: 'Listening address',
|
||||||
|
addressHelper:
|
||||||
|
'Specify the IP address monitored by the SSH service. The default value is 0.0.0.0. That is, all network interfaces are monitored.',
|
||||||
|
permitRootLogin: 'root user',
|
||||||
|
rootSettingHelper: 'The default login mode is SSH for user root.',
|
||||||
|
rootHelper1: 'Allow SSH login',
|
||||||
|
rootHelper2: 'Disable SSH login',
|
||||||
|
rootHelper3: 'Only key login is allowed',
|
||||||
|
rootHelper4: 'Only predefined commands can be executed. No other operations can be performed.',
|
||||||
|
passwordAuthentication: 'Password auth',
|
||||||
|
pwdAuthHelper: 'Whether to enable password authentication. This parameter is enabled by default.',
|
||||||
|
pubkeyAuthentication: 'Key auth',
|
||||||
|
key: 'Key',
|
||||||
|
pubkey: 'Key info',
|
||||||
|
encryptionMode: 'Encryption mode',
|
||||||
|
passwordHelper: 'Please enter a 6-10 digit encryption password',
|
||||||
|
generate: 'Generate key',
|
||||||
|
reGenerate: 'Regenerate key',
|
||||||
|
keyAuthHelper: 'Whether to enable key authentication. This parameter is enabled by default.',
|
||||||
|
useDNS: 'useDNS',
|
||||||
|
dnsHelper:
|
||||||
|
'Controls whether the DNS resolution function is enabled on the SSH server to verify the identity of the connection.',
|
||||||
|
loginLogs: 'SSH login log',
|
||||||
|
loginUser: 'User',
|
||||||
|
loginMode: 'Login mode',
|
||||||
|
authenticating: 'Key',
|
||||||
|
publickey: 'Key',
|
||||||
|
password: 'Password',
|
||||||
|
belong: 'Belong',
|
||||||
|
local: 'Local',
|
||||||
|
remote: 'Remote',
|
||||||
|
},
|
||||||
setting: {
|
setting: {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
panel: 'Panel',
|
panel: 'Panel',
|
||||||
|
|
|
@ -823,6 +823,7 @@ const message = {
|
||||||
ssh: {
|
ssh: {
|
||||||
sshChange: 'SSH 配置修改',
|
sshChange: 'SSH 配置修改',
|
||||||
sshChangeHelper: '确认将 SSH {0} 配置修改为 {1} 吗?',
|
sshChangeHelper: '确认将 SSH {0} 配置修改为 {1} 吗?',
|
||||||
|
sshFileChangeHelper: '直接修改配置文件可能会导致服务不可用,请谨慎操作,是否继续?',
|
||||||
port: '连接端口',
|
port: '连接端口',
|
||||||
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
|
||||||
listenAddress: '监听地址',
|
listenAddress: '监听地址',
|
||||||
|
@ -832,7 +833,7 @@ const message = {
|
||||||
rootHelper1: '允许 SSH 登录',
|
rootHelper1: '允许 SSH 登录',
|
||||||
rootHelper2: '禁止 SSH 登录',
|
rootHelper2: '禁止 SSH 登录',
|
||||||
rootHelper3: '仅允许密钥登录',
|
rootHelper3: '仅允许密钥登录',
|
||||||
rootHelper4: '仅允许带密码的密钥登录',
|
rootHelper4: '仅允许执行预先定义的命令,不能进行其他操作。',
|
||||||
passwordAuthentication: '密码认证',
|
passwordAuthentication: '密码认证',
|
||||||
pwdAuthHelper: '是否启用密码认证,默认启用。',
|
pwdAuthHelper: '是否启用密码认证,默认启用。',
|
||||||
pubkeyAuthentication: '密钥认证',
|
pubkeyAuthentication: '密钥认证',
|
||||||
|
@ -849,7 +850,11 @@ const message = {
|
||||||
loginUser: '用户',
|
loginUser: '用户',
|
||||||
loginMode: '登录方式',
|
loginMode: '登录方式',
|
||||||
authenticating: '密钥',
|
authenticating: '密钥',
|
||||||
|
publickey: '密钥',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
|
belong: '归属地',
|
||||||
|
local: '内网',
|
||||||
|
remote: '外网',
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
all: '全部',
|
all: '全部',
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
<el-tag type="success">{{ $t('commons.status.success') }}: {{ successfulCount }}</el-tag>
|
<el-tag type="success" class="tagClass" @click="onSearch('Success')">
|
||||||
<el-tag type="danger" style="margin-left: 5px">
|
{{ $t('commons.status.success') }}: {{ successfulCount }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag type="danger" class="tagClass" @click="onSearch('Failed')" style="margin-left: 5px">
|
||||||
{{ $t('commons.status.failed') }}: {{ faliedCount }}
|
{{ $t('commons.status.failed') }}: {{ faliedCount }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -16,8 +18,8 @@
|
||||||
<div class="search-button">
|
<div class="search-button">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchInfo"
|
v-model="searchInfo"
|
||||||
clearable
|
|
||||||
@clear="search()"
|
@clear="search()"
|
||||||
|
clearable
|
||||||
suffix-icon="Search"
|
suffix-icon="Search"
|
||||||
@keyup.enter="search()"
|
@keyup.enter="search()"
|
||||||
@change="search()"
|
@change="search()"
|
||||||
|
@ -29,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #search>
|
<template #search>
|
||||||
<el-select v-model="searchStatus" @change="search()" clearable>
|
<el-select v-model="searchStatus" @change="search()">
|
||||||
<template #prefix>{{ $t('commons.table.status') }}</template>
|
<template #prefix>{{ $t('commons.table.status') }}</template>
|
||||||
<el-option :label="$t('commons.table.all')" value="All"></el-option>
|
<el-option :label="$t('commons.table.all')" value="All"></el-option>
|
||||||
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
||||||
|
@ -38,9 +40,11 @@
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||||
<el-table-column min-width="40" :label="$t('logs.loginIP')" prop="ip">
|
<el-table-column min-width="60" :label="$t('logs.loginIP')" prop="address" />
|
||||||
<template #default="{ row }">{{ row.address }}:{{ row.port }}</template>
|
<el-table-column min-width="30" :label="$t('ssh.belong')" prop="isLocal">
|
||||||
|
<template #default="{ row }">{{ row.isLocal ? $t('ssh.local') : $t('ssh.remote') }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column min-width="40" :label="$t('firewall.port')" prop="port" />
|
||||||
<el-table-column min-width="40" :label="$t('ssh.loginMode')" prop="authMode">
|
<el-table-column min-width="40" :label="$t('ssh.loginMode')" prop="authMode">
|
||||||
<template #default="{ row }">{{ $t('ssh.' + row.authMode) }}</template>
|
<template #default="{ row }">{{ $t('ssh.' + row.authMode) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
@ -104,14 +108,33 @@ const search = async () => {
|
||||||
data.value = res.data.logs || [];
|
data.value = res.data.logs || [];
|
||||||
faliedCount.value = res.data.failedCount;
|
faliedCount.value = res.data.failedCount;
|
||||||
successfulCount.value = res.data.successfulCount;
|
successfulCount.value = res.data.successfulCount;
|
||||||
paginationConfig.total = res.data.failedCount + res.data.successfulCount;
|
if (searchStatus.value === 'Success') {
|
||||||
|
paginationConfig.total = res.data.successfulCount;
|
||||||
|
}
|
||||||
|
if (searchStatus.value === 'Failed') {
|
||||||
|
paginationConfig.total = res.data.failedCount;
|
||||||
|
}
|
||||||
|
if (searchStatus.value === 'All') {
|
||||||
|
paginationConfig.total = res.data.failedCount + res.data.successfulCount;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (status: string) => {
|
||||||
|
searchStatus.value = status;
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tagClass {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -117,8 +117,8 @@ 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 i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { getSSHInfo, updateSSH } from '@/api/modules/host';
|
import { getSSHInfo, updateSSH, updateSSHByfile } from '@/api/modules/host';
|
||||||
import { LoadFile, SaveFileContent } from '@/api/modules/files';
|
import { LoadFile } from '@/api/modules/files';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
|
||||||
|
@ -141,15 +141,21 @@ const form = reactive({
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSaveFile = async () => {
|
const onSaveFile = async () => {
|
||||||
loading.value = true;
|
ElMessageBox.confirm(i18n.global.t('ssh.sshFileChangeHelper'), i18n.global.t('ssh.sshChange'), {
|
||||||
await SaveFileContent({ path: '/etc/ssh/sshd_config', content: sshConf.value })
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
.then(() => {
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
loading.value = false;
|
type: 'info',
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
}).then(async () => {
|
||||||
})
|
loading.value = true;
|
||||||
.catch(() => {
|
await updateSSHByfile(sshConf.value)
|
||||||
loading.value = false;
|
.then(() => {
|
||||||
});
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOpenDrawer = () => {
|
const onOpenDrawer = () => {
|
||||||
|
@ -165,7 +171,7 @@ const onSave = async (formEl: FormInstance | undefined, key: string, value: stri
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.' + itemKey), value]),
|
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.' + itemKey), changei18n(value)]),
|
||||||
i18n.global.t('ssh.sshChange'),
|
i18n.global.t('ssh.sshChange'),
|
||||||
{
|
{
|
||||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
@ -196,6 +202,23 @@ function callback(error: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changei18n = (value: string) => {
|
||||||
|
switch (value) {
|
||||||
|
case 'yes':
|
||||||
|
return i18n.global.t('commons.button.enable');
|
||||||
|
case 'no':
|
||||||
|
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:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadSSHConf = async () => {
|
const loadSSHConf = async () => {
|
||||||
const res = await LoadFile({ path: '/etc/ssh/sshd_config' });
|
const res = await LoadFile({ path: '/etc/ssh/sshd_config' });
|
||||||
sshConf.value = res.data || '';
|
sshConf.value = res.data || '';
|
||||||
|
|
Loading…
Add table
Reference in a new issue