feat: Support editing ssh key information (#10579)

Refs #10537
This commit is contained in:
ssongliu 2025-10-09 14:39:25 +08:00 committed by GitHub
parent e4478da423
commit 99023569fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 418 additions and 248 deletions

View file

@ -70,42 +70,20 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
// @Tags SSH
// @Summary Generate host SSH secret
// @Accept json
// @Param request body dto.CreateRootCert true "request"
// @Param request body dto.RootCertOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/cert [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
func (b *BaseApi) CreateRootCert(c *gin.Context) {
var req dto.CreateRootCert
var req dto.RootCertOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.PassPhrase) != 0 {
passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PassPhrase = string(passPhrase)
if err := loadCertAfterDecrypt(&req); err != nil {
helper.BadRequest(c, err)
}
if len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PrivateKey = string(privateKey)
}
if len(req.PublicKey) != 0 {
publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey)
if err != nil {
helper.BadRequest(c, err)
return
}
req.PublicKey = string(publicKey)
}
if err := sshService.CreateRootCert(req); err != nil {
helper.InternalServer(c, err)
return
@ -113,6 +91,30 @@ func (b *BaseApi) CreateRootCert(c *gin.Context) {
helper.Success(c)
}
// @Tags SSH
// @Summary Update host SSH secret
// @Accept json
// @Param request body dto.RootCertOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /hosts/ssh/cert/update [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
func (b *BaseApi) EditRootCert(c *gin.Context) {
var req dto.RootCertOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := loadCertAfterDecrypt(&req); err != nil {
helper.BadRequest(c, err)
}
if err := sshService.EditRootCert(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
// @Tags SSH
// @Summary Sycn host SSH secret
// @Success 200
@ -265,3 +267,28 @@ func (b *BaseApi) UpdateSSHByFile(c *gin.Context) {
}
helper.Success(c)
}
func loadCertAfterDecrypt(req *dto.RootCertOperate) error {
if len(req.PassPhrase) != 0 {
passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase)
if err != nil {
return err
}
req.PassPhrase = string(passPhrase)
}
if len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return err
}
req.PrivateKey = string(privateKey)
}
if len(req.PublicKey) != 0 {
publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey)
if err != nil {
return err
}
req.PublicKey = string(publicKey)
}
return nil
}

View file

@ -22,7 +22,8 @@ type SSHInfo struct {
CurrentUser string `json:"currentUser"`
}
type CreateRootCert struct {
type RootCertOperate struct {
ID uint `json:"id"`
Name string `json:"name"`
Mode string `json:"mode"`
EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"`

View file

@ -19,7 +19,7 @@ type IHostRepo interface {
GetCert(opts ...DBOption) (model.RootCert, error)
PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error)
ListCert(opts ...DBOption) ([]model.RootCert, error)
CreateCert(cert *model.RootCert) error
SaveCert(cert *model.RootCert) error
UpdateCert(id uint, vars map[string]interface{}) error
DeleteCert(opts ...DBOption) error
}
@ -107,8 +107,8 @@ func (u *HostRepo) ListCert(opts ...DBOption) ([]model.RootCert, error) {
return ops, err
}
func (u *HostRepo) CreateCert(cert *model.RootCert) error {
return global.DB.Create(cert).Error
func (u *HostRepo) SaveCert(cert *model.RootCert) error {
return global.DB.Save(cert).Error
}
func (u *HostRepo) UpdateCert(id uint, vars map[string]interface{}) error {

View file

@ -46,7 +46,8 @@ type ISSHService interface {
ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error)
SyncRootCert() error
CreateRootCert(req dto.CreateRootCert) error
CreateRootCert(req dto.RootCertOperate) error
EditRootCert(req dto.RootCertOperate) error
SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error)
DeleteRootCerts(req dto.ForceDelete) error
}
@ -260,7 +261,7 @@ func (u *SSHService) SyncRootCert() error {
return hostRepo.SyncCert(rootCerts)
}
func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error {
func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error {
if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) {
return buserr.New("ErrCmdIllegal")
}
@ -280,6 +281,19 @@ func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error {
publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name)
authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys"
authFileItem, _ := os.ReadFile(authFilePath)
authFile := string(authFileItem)
if authFile != "" && !strings.HasSuffix(authFile, "\n") {
file, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer file.Close()
if _, err := file.WriteString("\n"); err != nil {
return err
}
}
if req.Mode == "input" || req.Mode == "import" {
if err := os.WriteFile(privatePath, []byte(req.PrivateKey), constant.FilePerm); err != nil {
return err
@ -308,7 +322,58 @@ func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error {
if len(cert.PassPhrase) != 0 {
cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase)
}
return hostRepo.CreateCert(&cert)
return hostRepo.SaveCert(&cert)
}
func (u *SSHService) EditRootCert(req dto.RootCertOperate) error {
currentUser, err := user.Current()
if err != nil {
return fmt.Errorf("load current user failed, err: %v", err)
}
certItem, _ := hostRepo.GetCert(repo.WithByID(req.ID))
if certItem.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
oldPublicItem, err := os.ReadFile(certItem.PublicKeyPath)
if err != nil {
return err
}
var cert model.RootCert
if err := copier.Copy(&cert, req); err != nil {
return err
}
cert.PrivateKeyPath = fmt.Sprintf("%s/.ssh/%s", currentUser.HomeDir, req.Name)
cert.PublicKeyPath = fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name)
if err := os.WriteFile(cert.PrivateKeyPath, []byte(req.PrivateKey), constant.FilePerm); err != nil {
return err
}
if err := os.WriteFile(cert.PublicKeyPath, []byte(req.PublicKey), constant.FilePerm); err != nil {
return err
}
authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys"
authItem, err := os.ReadFile(authFilePath)
if err != nil {
return err
}
oldPublic := strings.ReplaceAll(string(oldPublicItem), "\n", "")
newPublic := strings.ReplaceAll(string(req.PublicKey), "\n", "")
lines := strings.Split(string(authItem), "\n")
var newFiles []string
for i := 0; i < len(lines); i++ {
if len(lines[i]) != 0 && lines[i] != oldPublic && lines[i] != newPublic {
newFiles = append(newFiles, lines[i])
}
}
newFiles = append(newFiles, newPublic)
if err := os.WriteFile(authFilePath, []byte(strings.Join(newFiles, "\n")), constant.FilePerm); err != nil {
return fmt.Errorf("refresh authorized_keys failed, err: %v", err)
}
if len(cert.PassPhrase) != 0 {
cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase)
}
return hostRepo.SaveCert(&cert)
}
func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) {

View file

@ -529,7 +529,7 @@ var InitLocalSSHConn = &gormigrate.Migration{
itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel")
}
if _, err := os.Stat(itemPath); err != nil {
_ = service.NewISSHService().CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"})
_ = service.NewISSHService().CreateRootCert(dto.RootCertOperate{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"})
}
privateKey, _ := os.ReadFile(itemPath)
connWithKey := ssh.ConnInfo{

View file

@ -38,6 +38,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile)
hostRouter.POST("/ssh/cert", baseApi.CreateRootCert)
hostRouter.POST("/ssh/cert/update", baseApi.EditRootCert)
hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert)
hostRouter.POST("/ssh/cert/search", baseApi.SearchRootCert)
hostRouter.POST("/ssh/cert/delete", baseApi.DeleteRootCert)

View file

@ -183,6 +183,7 @@ export namespace Host {
id: number;
createAt: Date;
name: string;
mode: string;
encryptionMode: string;
passPhrase: string;
description: string;

View file

@ -93,6 +93,19 @@ export const createCert = (params: Host.RootCert) => {
}
return http.post(`/hosts/ssh/cert`, request);
};
export const editCert = (params: Host.RootCert) => {
let request = deepCopy(params) as Host.RootCert;
if (request.passPhrase) {
request.passPhrase = Base64.encode(request.passPhrase);
}
if (request.privateKey) {
request.privateKey = Base64.encode(request.privateKey);
}
if (request.publicKey) {
request.publicKey = Base64.encode(request.publicKey);
}
return http.post(`/hosts/ssh/cert/update`, request);
};
export const searchCert = (params: ReqPage) => {
return http.post<ResPage<Host.RootCertInfo>>(`/hosts/ssh/cert/search`, params);
};

View file

@ -4,7 +4,7 @@
<div class="mb-4">
<el-alert :closable="false">{{ $t('ssh.pubKeyHelper', [currentUser]) }}</el-alert>
</div>
<el-button type="primary" plain @click="onCreate()">
<el-button type="primary" plain @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}
</el-button>
<el-button plain @click="onSync()">
@ -33,115 +33,6 @@
</template>
</DrawerPro>
<DialogPro v-model="formOpen" :title="$t('commons.button.create')" size="w-60">
<div>
<el-form ref="formRef" label-position="top" :rules="rules" :model="form" v-loading="loading">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.encryptionMode')" prop="encryptionMode">
<el-select v-model="form.encryptionMode">
<el-option label="ED25519" value="ed25519" />
<el-option label="ECDSA" value="ecdsa" />
<el-option label="RSA" value="rsa" />
<el-option label="DSA" value="dsa" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('commons.login.password')" prop="passPhrase">
<el-input v-model="form.passPhrase" type="password" show-password>
<template #append>
<el-button @click="random">
{{ $t('commons.button.random') }}
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('ssh.createMode')" prop="privateKey">
<el-radio-group v-model="form.mode">
<el-radio value="generate">{{ $t('ssh.generate') }}</el-radio>
<el-radio value="input">{{ $t('ssh.input') }}</el-radio>
<el-radio value="import">{{ $t('ssh.import') }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="form.mode === 'input'">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.privateKey')" prop="privateKey">
<el-input type="textarea" :rows="2" v-model="form.privateKey" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('ssh.publicKey')" prop="publicKey">
<el-input type="textarea" :rows="2" v-model="form.publicKey" />
</el-form-item>
</el-col>
</el-row>
</div>
<div v-if="form.mode === 'import'">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.privateKey')" prop="privateKey">
<el-upload
action="#"
:auto-upload="false"
ref="uploadPrivateRef"
class="upload mt-2 w-full"
:limit="1"
:on-change="privateOnChange"
:on-exceed="privateExceed"
>
<el-button size="small" icon="Upload">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('ssh.publicKey')" prop="publicKey">
<el-upload
action="#"
:auto-upload="false"
ref="uploadPublicRef"
class="upload mt-2 w-full"
:limit="1"
:on-change="publicOnChange"
:on-exceed="publicExceed"
>
<el-button size="small" icon="Upload">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</div>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input v-model="form.description" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" :disabled="loading" @click="onConfirm(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
<DialogPro v-model="connOpen" :title="$t('ssh.pubkey')" size="small" :showClose="false">
<el-descriptions class="margin-top" :column="1" border>
<el-descriptions-item align="center" :label="$t('ssh.password')">
@ -198,16 +89,16 @@
</el-form>
</template>
</OpDialog>
<Operate ref="dialogRef" @search="search" />
</div>
</template>
<script lang="ts" setup>
import { Host } from '@/api/interface/host';
import { createCert, deleteCert, searchCert, syncCert } from '@/api/modules/host';
import { Rules } from '@/global/form-rules';
import { deleteCert, searchCert, syncCert } from '@/api/modules/host';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { copyText, getRandomStr } from '@/utils/util';
import { FormInstance, genFileId, UploadFile, UploadProps, UploadRawFile } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import Operate from '@/views/host/ssh/ssh/certification/operate/index.vue';
import { copyText } from '@/utils/util';
import { Base64 } from 'js-base64';
import { reactive, ref } from 'vue';
@ -229,37 +120,7 @@ const opRef = ref();
const currentRow = ref();
const connOpen = ref();
const formOpen = ref();
const formRef = ref();
const uploadPrivateRef = ref();
const uploadPublicRef = ref();
const currentUser = ref();
const form = reactive({
name: '',
mode: 'generate',
passPhrase: '',
encryptionMode: '',
privateKey: '',
publicKey: '',
description: '',
});
const rules = reactive({
name: Rules.simpleName,
encryptionMode: Rules.requiredSelect,
passPhrase: [{ validator: checkPassword, trigger: 'blur' }],
});
function checkPassword(rule: any, value: any, callback: any) {
if (form.passPhrase !== '') {
const reg = /^[A-Za-z0-9]{6,15}$/;
if (!reg.test(form.passPhrase)) {
return callback(new Error(i18n.global.t('ssh.passwordHelper')));
}
}
callback();
}
const acceptParams = async (user: string): Promise<void> => {
search();
@ -267,10 +128,6 @@ const acceptParams = async (user: string): Promise<void> => {
drawerVisible.value = true;
};
const random = async () => {
form.passPhrase = getRandomStr(10);
};
const loadPassPhrase = () => {
if (currentRow.value.passPhrase === '') {
return '-';
@ -284,72 +141,19 @@ const onCopy = async (content: string) => {
copyText(content);
};
const onCreate = () => {
form.name = '';
form.mode = 'generate';
form.encryptionMode = 'ed25519';
form.passPhrase = '';
form.privateKey = '';
form.publicKey = '';
form.description = '';
formOpen.value = true;
};
const onConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
await createCert(form)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
formOpen.value = false;
search();
})
.catch(() => {
loading.value = false;
});
});
};
const privateOnChange = (_uploadFile: UploadFile) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
form.privateKey = e.target.result as string;
} catch (error) {
MsgError(i18n.global.t('cronjob.errImport') + error.message);
}
const dialogRef = ref();
const onOpenDialog = async (
title: string,
rowData: Partial<Host.RootCertInfo> = {
mode: 'generate',
encryptionMode: 'ed25519',
},
) => {
let params = {
title,
rowData: { ...rowData },
};
reader.readAsText(_uploadFile.raw);
};
const privateExceed: UploadProps['onExceed'] = (files) => {
uploadPrivateRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadPrivateRef.value!.handleStart(file);
};
const publicOnChange = (_uploadFile: UploadFile) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
form.publicKey = e.target.result as string;
} catch (error) {
MsgError(i18n.global.t('cronjob.errImport') + error.message);
}
};
reader.readAsText(_uploadFile.raw);
};
const publicExceed: UploadProps['onExceed'] = (files) => {
uploadPublicRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadPublicRef.value!.handleStart(file);
};
const onCancel = () => {
formOpen.value = false;
dialogRef.value!.acceptParams(params);
};
const onDownload = async (row: Host.RootCertInfo, type: string) => {
@ -455,6 +259,12 @@ const handleClose = () => {
};
const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: (row: Host.RootCertInfo) => {
onOpenDialog('edit', row);
},
},
{
label: i18n.global.t('commons.button.view'),
click: (row: Host.RootCertInfo) => {

View file

@ -0,0 +1,252 @@
<template>
<DialogPro
v-model="drawerVisible"
:header="title"
@close="handleClose"
:resource="dialogData.title !== 'edit' ? '' : dialogData.rowData?.name"
size="large"
:autoClose="false"
:fullScreen="true"
>
<el-form ref="formRef" label-position="top" :rules="rules" :model="dialogData.rowData" v-loading="loading">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="dialogData.rowData.name" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.encryptionMode')" prop="encryptionMode">
<el-select v-model="dialogData.rowData.encryptionMode">
<el-option label="ED25519" value="ed25519" />
<el-option label="ECDSA" value="ecdsa" />
<el-option label="RSA" value="rsa" />
<el-option label="DSA" value="dsa" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('commons.login.password')" prop="passPhrase">
<el-input v-model="dialogData.rowData.passPhrase" type="password" show-password>
<template #append>
<el-button @click="random">
{{ $t('commons.button.random') }}
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('ssh.createMode')" prop="mode" v-if="dialogData.title === 'create'">
<el-radio-group v-model="dialogData.rowData.mode">
<el-radio value="generate">{{ $t('ssh.generate') }}</el-radio>
<el-radio value="input">{{ $t('ssh.input') }}</el-radio>
<el-radio value="import">{{ $t('ssh.import') }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="dialogData.rowData.mode === 'input'">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.privateKey')" prop="privateKey">
<el-input type="textarea" :rows="2" v-model="dialogData.rowData.privateKey" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('ssh.publicKey')" prop="publicKey">
<el-input type="textarea" :rows="2" v-model="dialogData.rowData.publicKey" />
</el-form-item>
</el-col>
</el-row>
</div>
<div v-if="dialogData.rowData.mode === 'import'">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="$t('ssh.privateKey')" prop="privateKey">
<el-upload
action="#"
:auto-upload="false"
ref="uploadPrivateRef"
class="upload mt-2 w-full"
:limit="1"
:on-change="privateOnChange"
:on-exceed="privateExceed"
>
<el-button size="small" icon="Upload">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('ssh.publicKey')" prop="publicKey">
<el-upload
action="#"
:auto-upload="false"
ref="uploadPublicRef"
class="upload mt-2 w-full"
:limit="1"
:on-change="publicOnChange"
:on-exceed="publicExceed"
>
<el-button size="small" icon="Upload">
{{ $t('commons.button.upload') }}
</el-button>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</div>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input v-model="dialogData.rowData.description" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="onConfirm(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { ElForm, genFileId, UploadFile, UploadProps, UploadRawFile } from 'element-plus';
import { Host } from '@/api/interface/host';
import { MsgError, MsgSuccess } from '@/utils/message';
import { Rules } from '@/global/form-rules';
import { getRandomStr } from '@/utils/util';
import { createCert, editCert } from '@/api/modules/host';
import { Base64 } from 'js-base64';
interface DialogProps {
title: string;
rowData?: Host.RootCertInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const dialogData = ref<DialogProps>({
title: '',
});
const loading = ref();
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref();
const uploadPrivateRef = ref();
const uploadPublicRef = ref();
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
if (params.title === 'edit') {
params.rowData.mode = 'input';
dialogData.value.rowData.publicKey = Base64.decode(params.rowData.publicKey);
dialogData.value.rowData.privateKey = Base64.decode(params.rowData.privateKey);
if (params.rowData.passPhrase) {
dialogData.value.rowData.passPhrase = Base64.decode(params.rowData.passPhrase);
}
}
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisible.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
function checkPassword(rule: any, value: any, callback: any) {
if (dialogData.value.rowData.passPhrase !== '') {
const reg = /^[A-Za-z0-9]{6,15}$/;
if (!reg.test(dialogData.value.rowData.passPhrase)) {
return callback(new Error(i18n.global.t('ssh.passwordHelper')));
}
}
callback();
}
const rules = reactive({
name: Rules.simpleName,
encryptionMode: Rules.requiredSelect,
passPhrase: [{ validator: checkPassword, trigger: 'blur' }],
privateKey: [Rules.requiredInput],
publicKey: [Rules.requiredInput],
});
const onConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
if (dialogData.value.title === 'create') {
await createCert(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
drawerVisible.value = false;
emit('search');
})
.catch(() => {
loading.value = false;
});
} else {
await editCert(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
drawerVisible.value = false;
emit('search');
})
.catch(() => {
loading.value = false;
});
}
});
};
const privateOnChange = (_uploadFile: UploadFile) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
dialogData.value.rowData.privateKey = e.target.result as string;
} catch (error) {
MsgError(i18n.global.t('cronjob.errImport') + error.message);
}
};
reader.readAsText(_uploadFile.raw);
};
const privateExceed: UploadProps['onExceed'] = (files) => {
uploadPrivateRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadPrivateRef.value!.handleStart(file);
};
const publicOnChange = (_uploadFile: UploadFile) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
dialogData.value.rowData.publicKey = e.target.result as string;
} catch (error) {
MsgError(i18n.global.t('cronjob.errImport') + error.message);
}
};
reader.readAsText(_uploadFile.raw);
};
const publicExceed: UploadProps['onExceed'] = (files) => {
uploadPublicRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadPublicRef.value!.handleStart(file);
};
const random = async () => {
dialogData.value.rowData.passPhrase = getRandomStr(10);
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>