feat: 网站创建增加 FTP 选项 (#5076)

This commit is contained in:
ssongliu 2024-05-21 16:42:46 +08:00 committed by GitHub
parent 5e951f028e
commit d9f9f9a629
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 228 additions and 51 deletions

View file

@ -122,7 +122,7 @@ func (b *BaseApi) CreateFtp(c *gin.Context) {
}
req.Password = string(pass)
}
if err := ftpService.Create(req); err != nil {
if _, err := ftpService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View file

@ -1,6 +1,8 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
@ -76,6 +78,14 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.FtpPassword) != 0 {
pass, err := base64.StdEncoding.DecodeString(req.FtpPassword)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.FtpPassword = string(pass)
}
err := websiteService.CreateWebsite(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)

View file

@ -27,6 +27,9 @@ type WebsiteCreate struct {
AppID uint `json:"appID"`
AppInstallID uint `json:"appInstallID"`
FtpUser string `json:"ftpUser"`
FtpPassword string `json:"ftpPassword"`
RuntimeID uint `json:"runtimeID"`
RuntimeConfig
}

View file

@ -26,6 +26,7 @@ type Website struct {
WebsiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"`
RuntimeID uint `gorm:"type:integer" json:"runtimeID"`
AppInstallID uint `gorm:"type:integer" json:"appInstallId"`
FtpID uint `gorm:"type:integer" json:"ftpId"`
User string `gorm:"type:varchar;" json:"user"`
Group string `gorm:"type:varchar;" json:"group"`

View file

@ -1,6 +1,9 @@
package service
import (
"fmt"
"os"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
@ -16,7 +19,7 @@ type IFtpService interface {
LoadBaseInfo() (dto.FtpBaseInfo, error)
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
Operate(operation string) error
Create(req dto.FtpCreate) error
Create(req dto.FtpCreate) (uint, error)
Delete(req dto.BatchDeleteReq) error
Update(req dto.FtpUpdate) error
Sync() error
@ -122,32 +125,39 @@ func (f *FtpService) Sync() error {
return nil
}
func (f *FtpService) Create(req dto.FtpCreate) error {
func (f *FtpService) Create(req dto.FtpCreate) (uint, error) {
if _, err := os.Stat(req.Path); err != nil {
if os.IsNotExist(err) {
fmt.Println(os.MkdirAll(req.Path, os.ModePerm))
} else {
return 0, err
}
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return err
return 0, err
}
userInDB, _ := ftpRepo.Get(hostRepo.WithByUser(req.User))
if userInDB.ID != 0 {
return constant.ErrRecordExist
return 0, constant.ErrRecordExist
}
client, err := toolbox.NewFtpClient()
if err != nil {
return err
return 0, err
}
if err := client.UserAdd(req.User, req.Password, req.Path); err != nil {
return err
return 0, err
}
var ftp model.Ftp
if err := copier.Copy(&ftp, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
return 0, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
ftp.Status = constant.StatusEnable
ftp.Password = pass
if err := ftpRepo.Create(&ftp); err != nil {
return err
return 0, err
}
return nil
return ftp.ID, nil
}
func (f *FtpService) Delete(req dto.BatchDeleteReq) error {

View file

@ -331,6 +331,14 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
return err
}
if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 {
itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: path.Join(global.CONF.System.BaseDir, "1panel/apps/openresty/openresty/www/sites", create.Alias, "index")})
if err != nil {
global.LOG.Errorf("create ftp for website failed, err: %v", err)
}
website.FtpID = itemID
}
if err = createWafConfig(website, domains); err != nil {
return err
}

View file

@ -205,9 +205,9 @@ var AddMonitorMenu = &gormigrate.Migration{
}
var AddFtp = &gormigrate.Migration{
ID: "20240517-add-ftp",
ID: "20240521-add-ftp",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Ftp{}); err != nil {
if err := tx.AutoMigrate(&model.Ftp{}, model.Website{}); err != nil {
return err
}
return nil

View file

@ -16,7 +16,8 @@ import (
)
type Ftp struct {
DefaultUser string
DefaultUser string
DefaultGroup string
}
type FtpClient interface {
@ -33,7 +34,11 @@ type FtpClient interface {
func NewFtpClient() (*Ftp, error) {
userItem, err := user.LookupId("1000")
if err == nil {
return &Ftp{DefaultUser: userItem.Username}, err
groupItem, err := user.LookupGroupId(userItem.Gid)
if err != nil {
return nil, err
}
return &Ftp{DefaultUser: userItem.Username, DefaultGroup: groupItem.Name}, err
}
if err.Error() != user.UnknownUserIdError(1000).Error() {
return nil, err
@ -45,7 +50,7 @@ func NewFtpClient() (*Ftp, error) {
if err != nil {
return nil, errors.New(stdout2)
}
return &Ftp{DefaultUser: "1panel"}, nil
return &Ftp{DefaultUser: "1panel", DefaultGroup: groupItem.Name}, nil
}
if err.Error() != user.UnknownGroupIdError("1000").Error() {
return nil, err
@ -54,11 +59,11 @@ func NewFtpClient() (*Ftp, error) {
if err != nil {
return nil, errors.New(string(stdout))
}
stdout2, err := cmd.Execf("useradd -u 1000 -g %s %s", groupItem.Name, userItem.Username)
stdout2, err := cmd.Execf("useradd -u 1000 -g 1panel %s", userItem.Username)
if err != nil {
return nil, errors.New(stdout2)
}
return &Ftp{DefaultUser: "1panel"}, nil
return &Ftp{DefaultUser: "1panel", DefaultGroup: "1panel"}, nil
}
func (f *Ftp) Status() (bool, bool) {
@ -87,7 +92,7 @@ func (f *Ftp) UserAdd(username, passwd, path string) error {
return errors.New(std)
}
_ = f.Reload()
std2, err := cmd.Execf("chown %s %s", f.DefaultUser, path)
std2, err := cmd.Execf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path)
if err != nil {
return errors.New(std2)
}
@ -170,19 +175,19 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) {
logItem := ""
if _, err := os.Stat("/etc/pure-ftpd/conf"); err != nil && os.IsNotExist(err) {
std, err := cmd.Exec("cat /etc/pure-ftpd/pure-ftpd.conf | grep AltLog | grep clf:")
if err != nil {
return logs, err
logItem = "/var/log/pureftpd.log"
if err == nil && !strings.HasPrefix(logItem, "#") {
logItem = std
}
logItem = std
} else {
if err != nil {
return logs, err
}
std, err := cmd.Exec("cat /etc/pure-ftpd/conf/AltLog")
if err != nil {
return nil, err
logItem = "/var/log/pure-ftpd/transfer.log"
if err != nil && !strings.HasPrefix(logItem, "#") {
logItem = std
}
logItem = std
}
logItem = strings.ReplaceAll(logItem, "AltLog", "")

View file

@ -19029,6 +19029,9 @@ const docTemplate = `{
"expireDate": {
"type": "string"
},
"ftpId": {
"type": "integer"
},
"group": {
"type": "string"
},
@ -20820,6 +20823,12 @@ const docTemplate = `{
"installed"
]
},
"ftpPassword": {
"type": "string"
},
"ftpUser": {
"type": "string"
},
"otherDomains": {
"type": "string"
},
@ -22062,6 +22071,9 @@ const docTemplate = `{
"expireDate": {
"type": "string"
},
"ftpId": {
"type": "integer"
},
"group": {
"type": "string"
},

View file

@ -19022,6 +19022,9 @@
"expireDate": {
"type": "string"
},
"ftpId": {
"type": "integer"
},
"group": {
"type": "string"
},
@ -20813,6 +20816,12 @@
"installed"
]
},
"ftpPassword": {
"type": "string"
},
"ftpUser": {
"type": "string"
},
"otherDomains": {
"type": "string"
},
@ -22055,6 +22064,9 @@
"expireDate": {
"type": "string"
},
"ftpId": {
"type": "integer"
},
"group": {
"type": "string"
},

View file

@ -3050,6 +3050,8 @@ definitions:
type: boolean
expireDate:
type: string
ftpId:
type: integer
group:
type: string
httpConfig:
@ -4248,6 +4250,10 @@ definitions:
- new
- installed
type: string
ftpPassword:
type: string
ftpUser:
type: string
otherDomains:
type: string
port:
@ -5080,6 +5086,8 @@ definitions:
type: string
expireDate:
type: string
ftpId:
type: integer
group:
type: string
httpConfig:

View file

@ -64,6 +64,8 @@ export namespace Website {
otherDomains: string;
proxy: string;
proxyType: string;
ftpUser: string;
ftpPassword: string;
}
export interface WebSiteUpdateReq {

View file

@ -3,6 +3,8 @@ import { ReqPage, ResPage } from '../interface';
import { Website } from '../interface/website';
import { File } from '../interface/file';
import { TimeoutEnum } from '@/enums/http-enum';
import { deepCopy } from '@/utils/util';
import { Base64 } from 'js-base64';
export const SearchWebsites = (req: Website.WebSiteSearch) => {
return http.post<ResPage<Website.WebsiteDTO>>(`/websites/search`, req);
@ -13,7 +15,11 @@ export const ListWebsites = () => {
};
export const CreateWebsite = (req: Website.WebSiteCreateReq) => {
return http.post<any>(`/websites`, req);
let request = deepCopy(req) as Website.WebSiteCreateReq;
if (request.ftpPassword) {
request.ftpPassword = Base64.encode(request.ftpPassword);
}
return http.post<any>(`/websites`, request);
};
export const OpWebsite = (req: Website.WebSiteOp) => {

View file

@ -181,6 +181,19 @@ const checkSimpleName = (rule: any, value: any, callback: any) => {
}
};
const checkSimplePassword = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.simplePassword')));
} else {
const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_]{5,29}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.simplePassword')));
} else {
callback();
}
}
};
const checkDBName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.dbName')));
@ -535,6 +548,7 @@ interface CommonRule {
name: FormItemRule;
userName: FormItemRule;
simpleName: FormItemRule;
simplePassword: FormItemRule;
dbName: FormItemRule;
imageName: FormItemRule;
volumeName: FormItemRule;
@ -601,6 +615,11 @@ export const Rules: CommonRule = {
validator: checkSimpleName,
trigger: 'blur',
},
simplePassword: {
required: true,
validator: checkSimplePassword,
trigger: 'blur',
},
dbName: {
required: true,
validator: checkDBName,

View file

@ -170,7 +170,8 @@ const message = {
commonName:
'Supports non-special characters starting with English, Chinese, numbers, .- and _, length 1-128',
userName: 'Support English, Chinese, numbers and _ length 3-30',
simpleName: 'Supports non-underscore starting, English, numbers, _, length 1-30',
simpleName: 'Supports non-underscore starting, English, numbers, _, length 3-30',
simplePassword: 'Supports non-underscore starting, English, numbers, _, length 6-30',
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
imageName: 'Support English, numbers, :/.-_, length 1-150',
volumeName: 'Support English, numbers, .-_, length 2-30',
@ -1736,6 +1737,10 @@ const message = {
zipFormat: '.tar.gz compressed package structure: test.tar.gz compressed package must contain {0} file',
proxy: 'Reverse Proxy',
alias: 'Path Name',
ftpUser: 'FTP Account',
ftpPassword: 'FTP Password',
ftpHelper:
'Create an FTP account corresponding to the site while creating the site, and the FTP directory points to the directory where the site is located.',
remark: 'Remark',
group: 'Group',
groupSetting: 'Group Management',

View file

@ -170,7 +170,8 @@ const message = {
illegalInput: '輸入框中存在不合法字符',
commonName: '支持非特殊字元開頭,英文中文數字.-和_,長度1-128',
userName: '支持英文中文數字和_,長度3-30',
simpleName: '支持非底線開頭英文數字_,長度1-30',
simpleName: '支持非底線開頭英文數字_,長度3-30',
simplePassword: '支持非底線開頭英文數字_,長度6-30',
dbName: '支持非特殊字符開頭英文中文數字.-_長度1-64',
imageName: '支持英文數字:/.-_,長度1-150',
volumeName: '支持英文數字.-和_,長度2-30',
@ -1617,6 +1618,9 @@ const message = {
zipFormat: '.tar.gz 壓縮包結構test.tar.gz 壓縮包內必需包含 {0} 文件',
proxy: '反向代理',
alias: '代號',
ftpUser: 'FTP 帳號',
ftpPassword: 'FTP 密碼',
ftpHelper: '建立站點的同時為站點建立一個對應 FTP 帳戶並且 FTP 目錄指向站點所在目錄',
remark: '備註',
group: '分組',
groupSetting: '分組管理',

View file

@ -171,6 +171,7 @@ const message = {
commonName: '支持非特殊字符开头,英文中文数字.-和_,长度1-128',
userName: '支持英文中文数字和_,长度3-30',
simpleName: '支持非下划线开头英文数字_,长度3-30',
simplePassword: '支持非下划线开头英文数字_,长度6-30',
dbName: '支持非特殊字符开头英文中文数字.-_,长度1-64',
imageName: '支持英文数字:/.-_,长度1-150',
volumeName: '支持英文数字.-和_,长度2-30',
@ -1617,6 +1618,10 @@ const message = {
zipFormat: '.tar.gz 压缩包结构test.tar.gz 压缩包内必需包含 {0} 文件',
proxy: '反向代理',
alias: '代号',
enableFtp: '创建 FTP',
ftpUser: 'FTP 账号',
ftpPassword: 'FTP 密码',
ftpHelper: '创建站点的同时为站点创建一个对应 FTP 帐户并且 FTP 目录指向站点所在目录',
remark: '备注',
group: '分组',
groupSetting: '分组管理',

View file

@ -154,7 +154,7 @@
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<span v-if="row.username === ''">-</span>
<div class="flex items-center" v-if="row.password && row.username">
<div class="flex items-center flex-wrap" v-if="row.password && row.username">
<div class="star-center" v-if="!row.showPassword">
<span>**********</span>
</div>

View file

@ -23,7 +23,7 @@
<el-table-column :label="$t('commons.login.username')" prop="username" />
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<div class="flex items-center">
<div class="flex items-center flex-wrap">
<div class="star-center">
<span v-if="!row.showPassword">**********</span>
</div>

View file

@ -131,7 +131,7 @@
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<span v-if="row.username === '' || row.password === ''">-</span>
<div class="flex items-center" v-else>
<div class="flex items-center flex-wrap" v-else>
<div class="star-center" v-if="!row.showPassword">
<span>**********</span>
</div>

View file

@ -23,7 +23,7 @@
<el-table-column :label="$t('commons.login.username')" prop="username" />
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<div class="flex items-center">
<div class="flex items-center flex-wrap">
<div class="star-center">
<span v-if="!row.showPassword">**********</span>
</div>

View file

@ -22,7 +22,7 @@
<el-table-column show-overflow-tooltip :label="$t('database.address')" prop="address" />
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<div class="flex items-center">
<div class="flex items-center flex-wrap">
<div class="star-center">
<span v-if="!row.showPassword">**********</span>
</div>

View file

@ -63,7 +63,7 @@
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<div v-if="row.password.length === 0">-</div>
<div v-else class="flex items-center">
<div v-else class="flex items-center flex-wrap">
<div class="star-center" v-if="!row.showPassword">
<span>**********</span>
</div>
@ -117,7 +117,11 @@
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('file.root')" :min-width="120" prop="path" show-overflow-tooltip />
<el-table-column :label="$t('file.root')" :min-width="120" prop="path">
<template #default="{ row }">
<Tooltip @click="toFolder(row.path)" :text="row.path" />
</template>
</el-table-column>
<el-table-column
:label="$t('commons.table.description')"
:min-width="80"
@ -126,12 +130,13 @@
/>
<el-table-column
:label="$t('commons.table.createdAt')"
show-overflow-tooltip
:formatter="dateFormat"
:min-width="80"
prop="createdAt"
/>
<fu-table-operations
width="240px"
width="200px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
@ -175,6 +180,7 @@ import { deleteFtp, searchFtp, updateFtp, syncFtp, operateFtp, getFtpBase } from
import OperateDialog from '@/views/toolbox/ftp/operate/index.vue';
import LogDialog from '@/views/toolbox/ftp/log/index.vue';
import { Toolbox } from '@/api/interface/toolbox';
import router from '@/routers';
const loading = ref();
const selects = ref<any>([]);
@ -232,6 +238,10 @@ const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/toolbox/ftp/', '_blank', 'noopener,noreferrer');
};
const toFolder = (folder: string) => {
router.push({ path: '/hosts/files', query: { path: folder } });
};
const onOperate = async (operation: string) => {
let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.';
ElMessageBox.confirm(i18n.global.t('toolbox.ftp.operation', [i18n.global.t(msg + operation)]), 'FTP', {
@ -348,7 +358,7 @@ const buttons = [
return row.status === 'deleted';
},
click: (row: Toolbox.FtpInfo) => {
dialogLogRef.value!.acceptParams({ user: row.user });
dialogLogRef.value!.acceptParams({ user: row.user, path: row.path });
},
},
{

View file

@ -15,9 +15,8 @@
<el-option value="GET" :label="$t('file.download')" />
</el-select>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column label="ip" prop="ip" />
<el-table-column :label="$t('commons.login.username')" prop="user" />
<ComplexTable class="mt-2" :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column label="ip" prop="ip" show-overflow-tooltip />
<el-table-column :label="$t('commons.table.status')" show-overflow-tooltip prop="status">
<template #default="{ row }">
<el-tag v-if="row.status === '200'">{{ $t('commons.status.success') }}</el-tag>
@ -26,12 +25,12 @@
</el-table-column>
<el-table-column :label="$t('commons.table.operate')" show-overflow-tooltip>
<template #default="{ row }">
{{ loadFileName(row.operation) }}
{{ loadOperation(row.operation) }}
</template>
</el-table-column>
<el-table-column :label="$t('file.file')" show-overflow-tooltip>
<template #default="{ row }">
{{ loadOperation(row.operation) }}
{{ loadFileName(row.operation) }}
</template>
</el-table-column>
<el-table-column :label="$t('file.size')" show-overflow-tooltip prop="size">
@ -66,14 +65,18 @@ const paginationConfig = reactive({
});
const data = ref();
const itemPath = ref();
interface DialogProps {
user: string;
path: string;
}
const loading = ref();
const drawerVisible = ref(false);
const acceptParams = (params: DialogProps): void => {
paginationConfig.user = params.user;
paginationConfig.operation = '';
itemPath.value = params.path;
search();
drawerVisible.value = true;
};
@ -101,7 +104,7 @@ const search = async () => {
});
};
const loadFileName = (operation: string) => {
const loadOperation = (operation: string) => {
if (operation.startsWith('"PUT')) {
return i18n.global.t('file.upload');
}
@ -109,8 +112,12 @@ const loadFileName = (operation: string) => {
return i18n.global.t('file.download');
}
};
const loadOperation = (operation: string) => {
return operation.replaceAll('"', '').replaceAll('PUT', '').replaceAll('GET', '');
const loadFileName = (operation: string) => {
return operation
.replaceAll('"', '')
.replaceAll('PUT', '')
.replaceAll('GET', '')
.replaceAll(itemPath.value + '/', '');
};
defineExpose({

View file

@ -14,7 +14,7 @@
:back="handleClose"
/>
</template>
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules" v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('commons.login.username')" prop="user">
@ -25,7 +25,11 @@
/>
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="password">
<el-input clearable v-model="dialogData.rowData!.password" />
<el-input type="password" clearable v-model="dialogData.rowData!.password" show-password>
<template #append>
<el-button @click="random">{{ $t('commons.button.random') }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('file.root')" prop="path">
<el-input v-model="dialogData.rowData!.path">
@ -43,7 +47,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="onSubmit(formRef)">
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@ -61,6 +65,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message';
import { Toolbox } from '@/api/interface/toolbox';
import { createFtp, updateFtp } from '@/api/modules/toolbox';
import { getRandomStr } from '@/utils/util';
interface DialogProps {
title: string;
@ -81,14 +86,18 @@ const acceptParams = (params: DialogProps): void => {
};
const emit = defineEmits<{ (e: 'search'): void }>();
const random = async () => {
dialogData.value.rowData.password = getRandomStr(16);
};
const handleClose = () => {
drawerVisible.value = false;
};
const rules = reactive({
user: [Rules.requiredInput, Rules.noSpace],
password: [Rules.requiredInput],
path: [Rules.requiredInput],
user: [Rules.simpleName],
password: [Rules.simplePassword],
path: [Rules.requiredInput, Rules.noSpace],
});
type FormInstance = InstanceType<typeof ElForm>;

View file

@ -300,6 +300,33 @@
</span>
</div>
</el-form-item>
<el-form-item prop="enableFtp" v-if="website.type === 'static' || website.type === 'runtime'">
<el-checkbox
@change="random"
v-model="website.enableFtp"
:label="$t('website.enableFtp')"
size="large"
/>
<span class="input-help">{{ $t('website.ftpHelper') }}</span>
</el-form-item>
<el-row :gutter="20" v-if="website.enableFtp">
<el-col :span="12">
<el-form-item prop="ftpUser" :label="$t('website.ftpUser')">
<el-input v-model="website.ftpUser" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="ftpPassword" :label="$t('website.ftpPassword')">
<el-input type="password" clearable v-model="website.ftpPassword" 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
v-if="website.type === 'proxy'"
:label="$t('website.proxyAddress')"
@ -316,7 +343,7 @@
</el-input>
</el-form-item>
<el-form-item :label="$t('website.remark')" prop="remark">
<el-input v-model="website.remark"></el-input>
<el-input type="textarea" :rows="3" clearable v-model="website.remark" />
</el-form-item>
</el-form>
</el-col>
@ -354,6 +381,7 @@ import { GetGroupList } from '@/api/modules/group';
import { Group } from '@/api/interface/group';
import { SearchRuntimes } from '@/api/modules/runtime';
import { Runtime } from '@/api/interface/runtime';
import { getRandomStr } from '@/utils/util';
const websiteForm = ref<FormInstance>();
const website = ref({
@ -383,6 +411,9 @@ const website = ref({
allowPort: false,
},
IPV6: false,
enableFtp: false,
ftpUser: '',
ftpPassword: '',
proxyType: 'tcp',
port: 9000,
proxyProtocol: 'http://',
@ -406,6 +437,8 @@ const rules = ref<any>({
memoryLimit: [Rules.requiredInput, checkNumberRange(0, 9999999999)],
containerName: [Rules.containerName],
},
ftpUser: [Rules.simpleName],
ftpPassword: [Rules.simplePassword],
proxyType: [Rules.requiredSelect],
port: [Rules.port],
runtimeType: [Rules.requiredInput],
@ -443,6 +476,10 @@ const handleClose = () => {
em('close', false);
};
const random = async () => {
website.value.ftpPassword = getRandomStr(16);
};
const changeType = (type: string) => {
switch (type) {
case 'deployment':
@ -614,6 +651,10 @@ const submit = async (formEl: FormInstance | undefined) => {
if (website.value.type === 'proxy') {
website.value.proxy = website.value.proxyProtocol + website.value.proxyAddress;
}
if (!website.value.enableFtp) {
website.value.ftpUser = '';
website.value.ftpPassword = '';
}
CreateWebsite(website.value)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));