mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-26 16:00:55 +00:00
+32
-25
@@ -67,28 +67,6 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Update host SSH setting by file
|
||||
// @Accept json
|
||||
// @Param request body dto.SSHConf true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/ssh/conffile/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
var req dto.SSHConf
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sshService.UpdateByFile(req.File); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Generate host SSH secret
|
||||
// @Accept json
|
||||
@@ -246,15 +224,44 @@ func (b *BaseApi) ExportSSHLogs(c *gin.Context) {
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Load host SSH conf
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {string} conf
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/ssh/conf [get]
|
||||
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
|
||||
data, err := sshService.LoadSSHConf()
|
||||
// @Router /hosts/ssh/file [post]
|
||||
func (b *BaseApi) LoadSSHFile(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := sshService.LoadSSHFile(req.Name)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags SSH
|
||||
// @Summary Update host SSH setting by file
|
||||
// @Accept json
|
||||
// @Param request body dto.SSHConf true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/ssh/file/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件 [key]","formatEN":"update SSH conf [key]"}
|
||||
func (b *BaseApi) UpdateSSHByFile(c *gin.Context) {
|
||||
var req dto.SettingUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sshService.UpdateByFile(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
+53
-25
@@ -38,9 +38,9 @@ type SSHService struct{}
|
||||
type ISSHService interface {
|
||||
GetSSHInfo() (*dto.SSHInfo, error)
|
||||
OperateSSH(operation string) error
|
||||
UpdateByFile(value string) error
|
||||
Update(req dto.SSHUpdate) error
|
||||
LoadSSHConf() (string, error)
|
||||
LoadSSHFile(name string) (string, error)
|
||||
UpdateByFile(req dto.SettingUpdate) error
|
||||
|
||||
LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error)
|
||||
ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error)
|
||||
@@ -223,25 +223,6 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SSHService) UpdateByFile(value string) error {
|
||||
serviceName, err := loadServiceName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.WriteString(value); err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.RunDefaultWithStdoutBashCf("%s systemctl restart %s", sudo, serviceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SSHService) SyncRootCert() error {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
@@ -500,17 +481,64 @@ func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string,
|
||||
return tmpFileName, nil
|
||||
}
|
||||
|
||||
func (u *SSHService) LoadSSHConf() (string, error) {
|
||||
if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
func (u *SSHService) LoadSSHFile(name string) (string, error) {
|
||||
var fileName string
|
||||
switch name {
|
||||
case "authKeys":
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load current user failed, err: %v", err)
|
||||
}
|
||||
fileName = currentUser.HomeDir + "/.ssh/authorized_keys"
|
||||
case "sshdConf":
|
||||
fileName = "/etc/ssh/sshd_config"
|
||||
default:
|
||||
return "", buserr.WithName("ErrNotSupportType", name)
|
||||
}
|
||||
content, err := os.ReadFile("/etc/ssh/sshd_config")
|
||||
if _, err := os.Stat(fileName); err != nil {
|
||||
return "", buserr.WithErr("ErrHttpReqNotFound", err)
|
||||
}
|
||||
content, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (u *SSHService) UpdateByFile(req dto.SettingUpdate) error {
|
||||
var fileName string
|
||||
switch req.Key {
|
||||
case "authKeys":
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load current user failed, err: %v", err)
|
||||
}
|
||||
fileName = currentUser.HomeDir + "/.ssh/authorized_keys"
|
||||
case "sshdConf":
|
||||
fileName = "/etc/ssh/sshd_config"
|
||||
default:
|
||||
return buserr.WithName("ErrNotSupportType", req.Key)
|
||||
}
|
||||
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.WriteString(req.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Key == "authKeys" {
|
||||
return nil
|
||||
}
|
||||
serviceName, err := loadServiceName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.RunDefaultWithStdoutBashCf("%s systemctl restart %s", sudo, serviceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortFileList(fileNames []sshFileItem) []sshFileItem {
|
||||
if len(fileNames) < 2 {
|
||||
return fileNames
|
||||
|
||||
@@ -29,13 +29,13 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.GET("/monitor/setting", baseApi.LoadMonitorSetting)
|
||||
hostRouter.POST("/monitor/setting/update", baseApi.UpdateMonitorSetting)
|
||||
|
||||
hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf)
|
||||
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)
|
||||
hostRouter.POST("/ssh/update", baseApi.UpdateSSH)
|
||||
hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs)
|
||||
hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs)
|
||||
hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile)
|
||||
hostRouter.POST("/ssh/operate", baseApi.OperateSSH)
|
||||
hostRouter.POST("/ssh/file", baseApi.LoadSSHFile)
|
||||
hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile)
|
||||
|
||||
hostRouter.POST("/ssh/cert", baseApi.CreateRootCert)
|
||||
hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert)
|
||||
|
||||
@@ -68,17 +68,17 @@ export const updateMonitorSetting = (key: string, value: string) => {
|
||||
export const getSSHInfo = () => {
|
||||
return http.post<Host.SSHInfo>(`/hosts/ssh/search`);
|
||||
};
|
||||
export const getSSHConf = () => {
|
||||
return http.get<string>(`/hosts/ssh/conf`);
|
||||
};
|
||||
export const operateSSH = (operation: string) => {
|
||||
return http.post(`/hosts/ssh/operate`, { operation: operation }, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const updateSSH = (params: Host.SSHUpdate) => {
|
||||
return http.post(`/hosts/ssh/update`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const updateSSHByfile = (file: string) => {
|
||||
return http.post(`/hosts/ssh/conffile/update`, { file: file }, TimeoutEnum.T_40S);
|
||||
export const loadSSHFile = (name: string) => {
|
||||
return http.post<string>(`/hosts/ssh/file`, { name: name });
|
||||
};
|
||||
export const updateSSHByFile = (key: string, file: string) => {
|
||||
return http.post(`/hosts/ssh/file/update`, { key: key, value: file }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const createCert = (params: Host.RootCert) => {
|
||||
let request = deepCopy(params) as Host.RootCert;
|
||||
|
||||
@@ -1576,6 +1576,8 @@ const message = {
|
||||
syncHelper: 'The sync operation will clean invalid keys and sync new complete key pairs. Continue?',
|
||||
input: 'Manual Input',
|
||||
import: 'File Upload',
|
||||
authKeys: 'Public Key Management',
|
||||
authKeysHelper: 'Save current public key information?',
|
||||
pubkey: 'Key info',
|
||||
pubKeyHelper: 'The current key information only takes effect for user {0}',
|
||||
encryptionMode: 'Encryption mode',
|
||||
|
||||
@@ -1572,6 +1572,8 @@ const message = {
|
||||
'La operación de sincronización limpiará las claves inválidas y sincronizará nuevos pares de claves completos. ¿Desea continuar?',
|
||||
input: 'Entrada manual',
|
||||
import: 'Subir archivo',
|
||||
authKeys: 'Gestión de Claves Públicas',
|
||||
authKeysHelper: '¿Guardar información actual de clave pública?',
|
||||
pubkey: 'Información de clave',
|
||||
pubKeyHelper: 'La información de la clave actual solo tiene efecto para el usuario {0}',
|
||||
encryptionMode: 'Modo de cifrado',
|
||||
|
||||
@@ -1523,6 +1523,8 @@ const message = {
|
||||
syncHelper: '同期操作は無効なキーをクリーンアップし、新しい完全なキーペアを同期します。続行しますか?',
|
||||
input: '手動入力',
|
||||
import: 'ファイルアップロード',
|
||||
authKeys: '公開鍵管理',
|
||||
authKeysHelper: '現在の公開鍵情報を保存しますか?',
|
||||
pubkey: '重要な情報',
|
||||
encryptionMode: '暗号化モード',
|
||||
pubKeyHelper: '現在の鍵情報はユーザー {0} にのみ有効です',
|
||||
|
||||
@@ -1506,6 +1506,8 @@ const message = {
|
||||
syncHelper: '동기화 작업으로 유효하지 않은 키를 정리하고 새로운 완전한 키 쌍을 동기화합니다. 계속하시겠습니까?',
|
||||
input: '수동 입력',
|
||||
import: '파일 업로드',
|
||||
authKeys: '공개 키 관리',
|
||||
authKeysHelper: '현재 공개 키 정보를 저장하시겠습니까?',
|
||||
pubkey: '키 정보',
|
||||
encryptionMode: '암호화 모드',
|
||||
pubKeyHelper: '현재 키 정보는 사용자 {0}에게만 적용됩니다',
|
||||
|
||||
@@ -1565,6 +1565,8 @@ const message = {
|
||||
'Operasi segerak akan membersihkan kunci tidak sah dan menyegerakkan pasangan kunci baru yang lengkap. Teruskan?',
|
||||
input: 'Input Manual',
|
||||
import: 'Muat Naik Fail',
|
||||
authKeys: 'Pengurusan Kunci Awam',
|
||||
authKeysHelper: 'Simpan maklumat kunci awam semasa?',
|
||||
pubkey: 'Maklumat kunci',
|
||||
pubKeyHelper: 'Maklumat kunci semasa hanya berkuat kuasa untuk pengguna {0}',
|
||||
encryptionMode: 'Mod penyulitan',
|
||||
|
||||
@@ -1554,6 +1554,8 @@ const message = {
|
||||
'A operação de sincronização limpará chaves inválidas e sincronizará novos pares de chaves completos. Continuar?',
|
||||
input: 'Entrada Manual',
|
||||
import: 'Upload de Arquivo',
|
||||
authKeys: 'Gerenciamento de Chaves Públicas',
|
||||
authKeysHelper: 'Salvar informações atuais da chave pública?',
|
||||
pubkey: 'Informações da chave',
|
||||
pubKeyHelper: 'A informação da chave atual só tem efeito para o usuário {0}',
|
||||
encryptionMode: 'Modo de criptografia',
|
||||
|
||||
@@ -1556,6 +1556,8 @@ const message = {
|
||||
'Операция синхронизации удалит недействительные ключи и синхронизирует новые полные ключевые пары. Продолжить?',
|
||||
input: 'Ручной ввод',
|
||||
import: 'Загрузка файла',
|
||||
authKeys: 'Управление Открытыми Ключами',
|
||||
authKeysHelper: 'Сохранить текущую информацию об открытом ключе?',
|
||||
pubkey: 'Информация о ключе',
|
||||
pubKeyHelper: 'Текущая информация о ключе действительна только для пользователя {0}',
|
||||
encryptionMode: 'Режим шифрования',
|
||||
|
||||
@@ -1594,6 +1594,8 @@ const message = {
|
||||
'Eşitleme işlemi geçersiz anahtarları temizleyecek ve yeni tam anahtar çiftlerini eşitleyecek. Devam edilsin mi?',
|
||||
input: 'Manuel Giriş',
|
||||
import: 'Dosya Yükleme',
|
||||
authKeys: 'Ortak Anahtar Yönetimi',
|
||||
authKeysHelper: 'Mevcut ortak anahtar bilgilerini kaydet?',
|
||||
pubkey: 'Anahtar bilgisi',
|
||||
pubKeyHelper: 'Mevcut anahtar bilgileri yalnızca {0} kullanıcısı için geçerlidir',
|
||||
encryptionMode: 'Şifreleme modu',
|
||||
|
||||
@@ -1500,6 +1500,8 @@ const message = {
|
||||
syncHelper: '同步操作將清理失效金鑰並同步新增的完整金鑰對,是否繼續?',
|
||||
input: '手動輸入',
|
||||
import: '文件上傳',
|
||||
authKeys: '公鑰管理',
|
||||
authKeysHelper: '是否儲存目前公鑰資訊?',
|
||||
pubkey: '金鑰資訊',
|
||||
pubKeyHelper: '目前金鑰資訊僅對使用者 {0} 生效',
|
||||
encryptionMode: '加密方式',
|
||||
|
||||
@@ -1496,6 +1496,8 @@ const message = {
|
||||
syncHelper: '同步操作将清理失效密钥并同步新增的完整密钥对,是否继续?',
|
||||
input: '手动输入',
|
||||
import: '文件上传',
|
||||
authKeys: '公钥管理',
|
||||
authKeysHelper: '是否保存当前公钥信息?',
|
||||
pubkey: '密钥信息',
|
||||
pubKeyHelper: '当前密钥信息仅对用户 {0} 生效',
|
||||
encryptionMode: '加密方式',
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<DrawerPro v-model="open" :header="$t('ssh.authKeys')" size="large">
|
||||
<div v-loading="loading">
|
||||
<CodemirrorPro
|
||||
:heightDiff="160"
|
||||
v-model="conf"
|
||||
:lineWrapping="true"
|
||||
placeholder="# The authorized_keys file does not exist or is empty (~/ssh/authorized_keys)"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" type="primary" @click="onSaveFile">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { loadSSHFile, updateSSHByFile } from '@/api/modules/host';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ref } from 'vue';
|
||||
const conf = ref();
|
||||
const loading = ref();
|
||||
const open = ref();
|
||||
|
||||
const acceptParams = async (): Promise<void> => {
|
||||
loadFile();
|
||||
open.value = true;
|
||||
};
|
||||
const loadFile = async () => {
|
||||
const res = await loadSSHFile('authKeys');
|
||||
conf.value = res.data || '';
|
||||
};
|
||||
const onSaveFile = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('ssh.authKeysHelper'), i18n.global.t('ssh.authKeys'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await updateSSHByFile('authKeys', conf.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
open.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -38,13 +38,16 @@
|
||||
</div>
|
||||
|
||||
<LayoutContent>
|
||||
<template #main>
|
||||
<template #leftToolBar>
|
||||
<el-radio-group v-model="confShowType" @change="changeMode">
|
||||
<el-radio-button value="base">{{ $t('database.baseConf') }}</el-radio-button>
|
||||
<el-radio-button value="all">{{ $t('database.allConf') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button @click="onOpenDrawer" class="mt-2 ml-2">{{ $t('ssh.pubkey') }}</el-button>
|
||||
<el-button @click="onOpenDrawer">{{ $t('ssh.pubkey') }}</el-button>
|
||||
<el-button @click="onOpenAuthKeys">{{ $t('ssh.authKeys') }}</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<el-row class="mt-10" v-if="confShowType === 'base'">
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="10" :xl="10">
|
||||
<el-form :model="form" label-position="right" ref="formRef" label-width="100px">
|
||||
@@ -111,7 +114,7 @@
|
||||
|
||||
<div v-if="confShowType === 'all'">
|
||||
<CodemirrorPro
|
||||
:heightDiff="450"
|
||||
:heightDiff="320"
|
||||
:minHeight="350"
|
||||
class="mt-5"
|
||||
v-model="sshConf"
|
||||
@@ -124,6 +127,7 @@
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<AuthKeys ref="authKeyRef" />
|
||||
<Cert ref="pubKeyRef" @search="search" />
|
||||
<Port ref="portRef" @search="search" />
|
||||
<Address ref="addressRef" @search="search" />
|
||||
@@ -134,13 +138,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import FireRouter from '@/views/host/ssh/index.vue';
|
||||
import AuthKeys from '@/views/host/ssh/ssh/auth-keys/index.vue';
|
||||
import Cert from '@/views/host/ssh/ssh/certification/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 { MsgSuccess } from '@/utils/message';
|
||||
import { getSSHConf, getSSHInfo, operateSSH, updateSSH, updateSSHByfile } from '@/api/modules/host';
|
||||
import { loadSSHFile, getSSHInfo, operateSSH, updateSSH, updateSSHByFile } from '@/api/modules/host';
|
||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
|
||||
@@ -151,6 +156,7 @@ const pubKeyRef = ref();
|
||||
const portRef = ref();
|
||||
const addressRef = ref();
|
||||
const rootsRef = ref();
|
||||
const authKeyRef = ref();
|
||||
|
||||
const autoStart = ref('enable');
|
||||
|
||||
@@ -178,7 +184,7 @@ const onSaveFile = async () => {
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await updateSSHByfile(sshConf.value)
|
||||
await updateSSHByFile('sshdConf', sshConf.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -192,6 +198,9 @@ const onSaveFile = async () => {
|
||||
const onOpenDrawer = () => {
|
||||
pubKeyRef.value.acceptParams(form.currentUser);
|
||||
};
|
||||
const onOpenAuthKeys = () => {
|
||||
authKeyRef.value.acceptParams();
|
||||
};
|
||||
|
||||
const onChangePort = () => {
|
||||
portRef.value.acceptParams({ port: form.port });
|
||||
@@ -285,7 +294,7 @@ const changeI18n = (value: string) => {
|
||||
};
|
||||
|
||||
const loadSSHConf = async () => {
|
||||
const res = await getSSHConf();
|
||||
const res = await loadSSHFile('sshdConf');
|
||||
sshConf.value = res.data || '';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user