feat: 存储卷创建支持 nfs (#795)

This commit is contained in:
ssongliu 2023-04-26 17:26:12 +08:00 committed by GitHub
parent 0c09b12680
commit 4377575206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 24 deletions

View File

@ -324,8 +324,8 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
func stringsToMap(list []string) map[string]string { func stringsToMap(list []string) map[string]string {
var lableMap = make(map[string]string) var lableMap = make(map[string]string)
for _, label := range list { for _, label := range list {
sps := strings.Split(label, "=") if strings.Contains(label, "=") {
if len(sps) > 1 { sps := strings.SplitN(label, "=", 2)
lableMap[sps[0]] = sps[1] lableMap[sps[0]] = sps[1]
} }
} }

View File

@ -40,10 +40,10 @@ type daemonJsonItem struct {
func (u *DockerService) LoadDockerStatus() string { func (u *DockerService) LoadDockerStatus() string {
status := constant.StatusRunning status := constant.StatusRunning
stdout, err := cmd.Exec("systemctl is-active docker") // stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) != "active\n" || err != nil { // if string(stdout) != "active\n" || err != nil {
status = constant.Stopped // status = constant.Stopped
} // }
return status return status
} }

View File

@ -3,7 +3,7 @@ const message = {
commons: { commons: {
true: 'true', true: 'true',
false: 'false', false: 'false',
example: 'Such as', example: 'e.g.:',
button: { button: {
create: 'Create ', create: 'Create ',
add: 'Add ', add: 'Add ',
@ -521,7 +521,12 @@ const message = {
volume: 'Volume', volume: 'Volume',
volumeName: 'Name', volumeName: 'Name',
nfsEnable: 'Enable NFS storage',
nfsAddress: 'Address',
nfsAddressHelper: 'Support input of IP address or domain',
mountpoint: 'Mountpoint', mountpoint: 'Mountpoint',
mountpointNFSHerlper: 'e.g. /nfs, /nfs-share',
options: 'Options',
createVolume: 'Create volume', createVolume: 'Create volume',
repo: 'Repo', repo: 'Repo',
@ -871,7 +876,7 @@ const message = {
'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters', 'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters',
mfa: 'MFA', mfa: 'MFA',
mfaHelper: 'After this function is enabled, the mobile application verification code will be verified', mfaHelper: 'After this function is enabled, the mobile application verification code will be verified',
mfaHelper1: 'Download a MFA verification mobile app such as:', mfaHelper1: 'Download a MFA verification mobile app e.g.:',
mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code', mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code',
mfaHelper3: 'Enter six digits from the app', mfaHelper3: 'Enter six digits from the app',
@ -1195,7 +1200,7 @@ const message = {
'If the website cannot be accessed normally after setting pseudo-static, please try to set it back to default', 'If the website cannot be accessed normally after setting pseudo-static, please try to set it back to default',
runDir: 'Run Directory', runDir: 'Run Directory',
runDirHelper: runDirHelper:
'Some programs need to specify a secondary directory as the running directory, such as ThinkPHP5, Laravel', 'Some programs need to specify a secondary directory as the running directory, e.g. ThinkPHP5, Laravel',
runUserHelper: runUserHelper:
'For websites deployed through the PHP runtime environment, all files, folder owners, and user groups under the index and subdirectories need to be set to 1000, command: chown -R 1000:1000 index', 'For websites deployed through the PHP runtime environment, all files, folder owners, and user groups under the index and subdirectories need to be set to 1000, command: chown -R 1000:1000 index',
userGroup: 'User/Group', userGroup: 'User/Group',
@ -1332,11 +1337,11 @@ const message = {
'Change the port policy to [accept]. After the port policy is set, normal port access will be restored. Do you want to continue?', 'Change the port policy to [accept]. After the port policy is set, normal port access will be restored. Do you want to continue?',
stop: 'Stop', stop: 'Stop',
portFormatError: 'Please enter the correct port information!', portFormatError: 'Please enter the correct port information!',
portHelper1: 'Multiple ports, such as 8080 and 8081', portHelper1: 'Multiple ports, e.g. 8080 and 8081',
portHelper2: 'Range port, such as 8080-8089', portHelper2: 'Range port, e.g. 8080-8089',
changeStrategyHelper: changeStrategyHelper:
'Change [{1}] {0} strategy to [{2}]. After setting, {0} will access {2} externally. Do you want to continue?', 'Change [{1}] {0} strategy to [{2}]. After setting, {0} will access {2} externally. Do you want to continue?',
portHelper: 'Multiple ports can be entered, such as 80,81, or range ports, such as 80-88', portHelper: 'Multiple ports can be entered, e.g. 80,81, or range ports, e.g. 80-88',
strategy: 'Strategy', strategy: 'Strategy',
accept: 'Accept', accept: 'Accept',
drop: 'Drop', drop: 'Drop',
@ -1346,8 +1351,8 @@ const message = {
allow: 'Allow', allow: 'Allow',
deny: 'Deny', deny: 'Deny',
addressFormatError: 'Please enter a valid ip address!', addressFormatError: 'Please enter a valid ip address!',
addressHelper1: 'Multiple IP please separated with ",", such as 172.16.10.11, 172.16.10.99', addressHelper1: 'Multiple IP please separated with ",", e.g. 172.16.10.11, 172.16.10.99',
addressHelper2: 'IP segment, such as 172.16.10.0/24', addressHelper2: 'IP segment, e.g. 172.16.10.0/24',
allIP: 'All IP', allIP: 'All IP',
portRule: 'Port rule', portRule: 'Port rule',
ipRule: 'IP rule', ipRule: 'IP rule',

View File

@ -3,7 +3,7 @@ const message = {
commons: { commons: {
true: '是', true: '是',
false: '否', false: '否',
example: '', example: '',
button: { button: {
create: '创建', create: '创建',
add: '添加', add: '添加',
@ -473,8 +473,8 @@ const message = {
port: '端口', port: '端口',
server: '服务器', server: '服务器',
serverExample: ' 80, 80-88, ip:80 或者 ip:80-88', serverExample: ' 80, 80-88, ip:80 或者 ip:80-88',
contianerExample: ' 80 或者 80-88', contianerExample: ' 80 或者 80-88',
exposePort: '暴露端口', exposePort: '暴露端口',
exposeAll: '暴露所有', exposeAll: '暴露所有',
cmd: '启动命令', cmd: '启动命令',
@ -540,8 +540,13 @@ const message = {
gateway: '网关', gateway: '网关',
volume: '存储卷', volume: '存储卷',
nfsEnable: '启用 NFS 存储',
nfsAddress: '地址',
nfsAddressHelper: '支持输入 ip 或者域名',
volumeName: '名称', volumeName: '名称',
mountpoint: '挂载点', mountpoint: '挂载点',
mountpointNFSHerlper: '/nfs, /nfs-share',
options: '可选参数',
createVolume: '创建存储卷', createVolume: '创建存储卷',
repo: '仓库', repo: '仓库',
@ -597,7 +602,7 @@ const message = {
record: '报告', record: '报告',
shell: 'Shell 脚本', shell: 'Shell 脚本',
website: '备份网站', website: '备份网站',
rulesHelper: '压缩排除规则( ; 号为分隔符) \n*.log;*.sql', rulesHelper: '压缩排除规则( ; 号为分隔符) \n*.log;*.sql',
lastRecrodTime: '上次执行时间', lastRecrodTime: '上次执行时间',
database: '备份数据库', database: '备份数据库',
missBackupAccount: '未能找到备份账号', missBackupAccount: '未能找到备份账号',
@ -896,7 +901,7 @@ const message = {
upgrading: '正在升级中请稍候...', upgrading: '正在升级中请稍候...',
upgradeHelper: '升级操作需要重启服务是否继续', upgradeHelper: '升级操作需要重启服务是否继续',
noUpgrade: '当前已经是最新版本', noUpgrade: '当前已经是最新版本',
versionHelper: '1Panel 版本号命名规则为 [大版本].[功能版本].[Bug 修复版本]如下', versionHelper: '1Panel 版本号命名规则为 [大版本].[功能版本].[Bug 修复版本]',
versionHelper1: 'v1.0.1 v1.0.0 之后的 Bug 修复版本', versionHelper1: 'v1.0.1 v1.0.0 之后的 Bug 修复版本',
versionHelper2: 'v1.1.0 v1.0.0 之后的功能版本', versionHelper2: 'v1.1.0 v1.0.0 之后的功能版本',
newVersion: '(Bug 修复版本)', newVersion: '(Bug 修复版本)',
@ -1116,7 +1121,7 @@ const message = {
value: '值', value: '值',
enable: '开启', enable: '开启',
proxyAddress: '代理地址', proxyAddress: '代理地址',
proxyHelper: ': http://127.0.0.1:8080', proxyHelper: ': http://127.0.0.1:8080',
forceDelete: '强制删除', forceDelete: '强制删除',
forceDeleteHelper: '强制删除会忽略删除过程中产生的错误并最终删除元数据', forceDeleteHelper: '强制删除会忽略删除过程中产生的错误并最终删除元数据',
deleteAppHelper: '同时删除关联应用数据库以及应用备份', deleteAppHelper: '同时删除关联应用数据库以及应用备份',
@ -1356,7 +1361,7 @@ const message = {
localHelper: '本地运行环境需要自行安装', localHelper: '本地运行环境需要自行安装',
version: '版本', version: '版本',
status: '状态', status: '状态',
versionHelper: 'PHP的版本, v8.0', versionHelper: 'PHP的版本, v8.0',
buildHelper: buildHelper:
'选择的扩展越多制作镜像过程中占用 CPU 越多请尽量避免选择全部扩展如果没有想要的扩展可以手动输入之后选择', '选择的扩展越多制作镜像过程中占用 CPU 越多请尽量避免选择全部扩展如果没有想要的扩展可以手动输入之后选择',
openrestryWarn: 'PHP 需要升级 OpenResty 1.21.4.1 版本以上才能使用', openrestryWarn: 'PHP 需要升级 OpenResty 1.21.4.1 版本以上才能使用',

View File

@ -17,10 +17,36 @@
<el-input clearable v-model.trim="form.name" /> <el-input clearable v-model.trim="form.name" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.driver')" prop="driver"> <el-form-item :label="$t('container.driver')" prop="driver">
<el-select v-model="form.driver"> <el-tag type="success">local</el-tag>
<el-option label="local" value="local" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('container.nfsEnable')" prop="nfsStatus">
<el-switch v-model="form.nfsStatus" active-value="enable" inactive-value="disable" />
</el-form-item>
<div v-if="form.nfsStatus === 'enable'">
<el-form-item :label="$t('container.nfsAddress')" prop="nfsAddress">
<el-input
clearable
v-model.trim="form.nfsAddress"
:placeholder="$t('container.nfsAddressHelper')"
/>
</el-form-item>
<el-form-item :label="$t('container.version')" prop="nfsVersion">
<el-radio-group v-model="form.nfsVersion">
<el-radio label="v3">NFS</el-radio>
<el-radio label="v4">NFS4</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('container.mountpoint')" prop="nfsMount">
<el-input
clearable
v-model.trim="form.nfsMount"
:placeholder="$t('container.mountpointNFSHerlper')"
/>
</el-form-item>
<el-form-item :label="$t('container.options')" prop="nfsOption">
<el-input clearable v-model.trim="form.nfsOption" />
</el-form-item>
</div>
<el-form-item :label="$t('container.option')" prop="optionStr"> <el-form-item :label="$t('container.option')" prop="optionStr">
<el-input <el-input
type="textarea" type="textarea"
@ -70,6 +96,11 @@ const form = reactive({
driver: 'local', driver: 'local',
labelStr: '', labelStr: '',
labels: [] as Array<string>, labels: [] as Array<string>,
nfsStatus: 'disable',
nfsAddress: '',
nfsVersion: 'v4',
nfsMount: '',
nfsOption: 'rw,noatime,rsize=8192,wsize=8192,tcp,timeo=14',
optionStr: '', optionStr: '',
options: [] as Array<string>, options: [] as Array<string>,
}); });
@ -80,6 +111,11 @@ const acceptParams = (): void => {
form.labelStr = ''; form.labelStr = '';
form.options = []; form.options = [];
form.optionStr = ''; form.optionStr = '';
form.nfsStatus = 'disable';
form.nfsAddress = '';
form.nfsVersion = 'v4';
form.nfsMount = '';
form.nfsOption = 'rw,noatime,rsize=8192,wsize=8192,tcp,timeo=14';
drawerVisiable.value = true; drawerVisiable.value = true;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -91,6 +127,9 @@ const handleClose = () => {
const rules = reactive({ const rules = reactive({
name: [Rules.requiredInput, Rules.volumeName], name: [Rules.requiredInput, Rules.volumeName],
driver: [Rules.requiredSelect], driver: [Rules.requiredSelect],
nfsAddress: [Rules.host],
nfsVersion: [Rules.requiredSelect],
nfsMount: [Rules.requiredInput],
}); });
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
@ -106,6 +145,13 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (form.optionStr !== '') { if (form.optionStr !== '') {
form.options = form.optionStr.split('\n'); form.options = form.optionStr.split('\n');
} }
if (form.nfsStatus === 'enable') {
let typeOption = form.nfsVersion === 'v4' ? 'nfs4' : 'nfs';
form.options.push('type=' + typeOption);
form.options.push('o=addr=' + form.nfsAddress + ',' + form.nfsOption);
let mount = form.nfsMount.startsWith(':') ? form.nfsMount : ':' + form.nfsMount;
form.options.push('device=' + mount);
}
loading.value = true; loading.value = true;
await createVolume(form) await createVolume(form)
.then(() => { .then(() => {