1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 00:09:16 +08:00

feat: Merge code from dev (#7726)

This commit is contained in:
ssongliu 2025-01-15 14:24:24 +08:00 committed by GitHub
parent bb250557a2
commit b90a90c0c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 124 additions and 103 deletions

View File

@ -7,24 +7,23 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"io"
"os"
"path"
"strings"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/homedir"
@ -419,6 +418,9 @@ func (u *ImageService) ImageRemove(req dto.BatchDelete) (dto.ContainerPruneRepor
}
return report, buserr.WithDetail(constant.ErrInUsed, id, nil)
}
if strings.Contains(err.Error(), "image has dependent") {
return report, buserr.New(constant.ErrObjectBeDependent)
}
return report, err
}
report.DeletedNumber++

View File

@ -103,10 +103,11 @@ var (
// container
var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
ErrPgImagePull = "ErrPgImagePull"
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrObjectBeDependent = "ErrObjectBeDependent"
ErrPortRules = "ErrPortRules"
ErrPgImagePull = "ErrPgImagePull"
)
// runtime

View File

@ -157,6 +157,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrObjectBeDependent: "This image is dependent on other images and can't be deleted"
ErrPortRules: "The number of ports does not match, please re-enter!"
ErrPgImagePull: "Image pull timeout. Please configure image acceleration or manually pull the postgres:16.0-alpine image and try again"

View File

@ -158,6 +158,7 @@ ErrTypeOfRedis: "恢復文件類型與當前持久化方式不符,請修改後
#container
ErrInUsed: "{{ .detail }} 正被使用,無法刪除"
ErrObjectInUsed: "該對象正被使用,無法刪除"
ErrObjectBeDependent: "該鏡像依賴於其他鏡像,無法刪除"
ErrPortRules: "端口數目不匹配,請重新輸入!"
ErrPgImagePull: "鏡像拉取超時,請配置鏡像加速或手動拉取 postgres:16.0-alpine 鏡像後重試"

View File

@ -156,8 +156,9 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrObjectBeDependent: "该镜像依赖于其他镜像,无法删除"
ErrPortRules: "端口数目不匹配,请重新输入!"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 postgres:16.0-alpine 镜像后重试"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"

View File

@ -25,12 +25,12 @@ func (f *Firewall) Name() string {
}
func (f *Firewall) Status() (bool, error) {
stdout, _ := cmd.Exec("firewall-cmd --state")
stdout, _ := cmd.Exec("LANGUAGE=en_US:en firewall-cmd --state")
return stdout == "running\n", nil
}
func (f *Firewall) Version() (string, error) {
stdout, err := cmd.Exec("firewall-cmd --version")
stdout, err := cmd.Exec("LANGUAGE=en_US:en firewall-cmd --version")
if err != nil {
return "", fmt.Errorf("load the firewall version failed, err: %s", stdout)
}

View File

@ -17,9 +17,9 @@ type Ufw struct {
func NewUfw() (*Ufw, error) {
var ufw Ufw
if cmd.HasNoPasswordSudo() {
ufw.CmdStr = "sudo ufw"
ufw.CmdStr = "LANGUAGE=en_US:en sudo ufw"
} else {
ufw.CmdStr = "ufw"
ufw.CmdStr = "LANGUAGE=en_US:en ufw"
}
return &ufw, nil
}

View File

@ -306,7 +306,7 @@ func loadImageTag() (string, error) {
defer cancel()
if _, err := client.ImagePull(ctx, itemTag, image.PullOptions{}); err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return itemTag, buserr.New(constant.ErrPgImagePull)
return itemTag, buserr.WithName(constant.ErrPgImagePull, itemTag)
}
global.LOG.Errorf("image %s pull failed, err: %v", itemTag, err)
return itemTag, fmt.Errorf("image %s pull failed, err: %v", itemTag, err)

View File

@ -20,10 +20,7 @@
"prettier": "prettier --write ."
},
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-php": "^6.0.1",
"@codemirror/language": "^6.10.2",
"@codemirror/legacy-modes": "^6.4.0",
"@codemirror/theme-one-dark": "^6.1.2",

View File

@ -134,7 +134,6 @@ const onCheck = async (key: any, name: any) => {
};
const onOperate = async (operation: string) => {
em('update:maskShow', false);
operateReq.operate = operation;
ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('app.' + operation)]),
@ -144,25 +143,21 @@ const onOperate = async (operation: string) => {
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
)
.then(() => {
em('update:maskShow', true);
em('update:loading', true);
em('before');
InstalledOp(operateReq)
.then(() => {
em('update:loading', false);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
onCheck(key.value, name.value);
em('after');
})
.catch(() => {
em('update:loading', false);
});
})
.catch(() => {
em('update:maskShow', true);
});
).then(() => {
em('update:maskShow', true);
em('update:loading', true);
em('before');
InstalledOp(operateReq)
.then(() => {
em('update:loading', false);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
onCheck(key.value, name.value);
em('after');
})
.catch(() => {
em('update:loading', false);
});
});
};
onMounted(() => {

View File

@ -130,7 +130,6 @@ const taskLogRef = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'backup-page-size',
currentPage: 1,
pageSize: 10,
total: 0,

View File

@ -8,13 +8,13 @@
import { CSSProperties } from 'vue';
import { basicSetup, EditorView } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { StreamLanguage } from '@codemirror/language';
import { nginx } from './nginx';
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { javascript } from '@codemirror/legacy-modes/mode/javascript';
import { placeholder } from '@codemirror/view';
import { json } from '@codemirror/lang-json';
@ -94,7 +94,7 @@ const initCodeMirror = () => {
extensions.push(StreamLanguage.define(dockerFile));
break;
case 'javascript':
extensions.push(javascript());
extensions.push(StreamLanguage.define(javascript));
break;
case 'nginx':
extensions.push(StreamLanguage.define(nginx));

View File

@ -1,6 +1,6 @@
<template>
<el-button v-if="type == 'icon'" link @click="copyText(content)" icon="DocumentCopy" class="ml-1.5"></el-button>
<el-button type="primary" @click="copyText(content)" v-else>{{ $t('commons.button.copy') }}</el-button>
<el-button @click="copyText(content)" v-else>{{ $t('commons.button.copy') }}</el-button>
</template>
<script lang="ts" setup>

View File

@ -153,7 +153,6 @@ const open = ref();
const data = ref();
const title = ref();
const paginationConfig = reactive({
cacheSizeKey: 'upload-page-size',
currentPage: 1,
pageSize: 10,
total: 0,

View File

@ -224,6 +224,19 @@ const checkDBName = (rule: any, value: any, callback: any) => {
}
};
const checkComposeName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.composeName')));
} else {
const reg = /^[a-z0-9]{1}[a-z0-9_-]{0,256}$/;
if (!reg.test(value) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.composeName')));
} else {
callback();
}
}
};
const checkImageName = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.imageName')));
@ -568,6 +581,7 @@ interface CommonRule {
simplePassword: FormItemRule;
dbName: FormItemRule;
imageName: FormItemRule;
composeName: FormItemRule;
volumeName: FormItemRule;
linuxName: FormItemRule;
password: FormItemRule;
@ -643,6 +657,11 @@ export const Rules: CommonRule = {
validator: checkDBName,
trigger: 'blur',
},
composeName: {
required: true,
validator: checkComposeName,
trigger: 'blur',
},
imageName: {
required: true,
validator: checkImageName,

View File

@ -201,6 +201,8 @@ const message = {
simpleName: 'Supports non-underscore starting, English, numbers, _, length 3-30',
simplePassword: 'Supports non-underscore starting, English, numbers, _, length 1-30',
dbName: 'Supports non-special character starting, including English, Chinese, numbers, .-_, with a length of 1-64',
composeName:
'Supports non-special characters at the beginning, lowercase letters, numbers, - and _, length 1-256',
imageName: 'Support English, numbers, :@/.-_, length 1-256',
volumeName: 'Support English, numbers, .-_, length 2-30',
supervisorName: 'Supports non-special characters starting with English, numbers, - and _, length 1-128',
@ -949,6 +951,14 @@ const message = {
retainCopiesUnit: ' copies (View)',
cronSpecRule: 'The execution period format in line {0} is incorrect. Please check and try again!',
cronSpecRule2: 'Execution period format is incorrect, please check and try again!',
perMonthHelper: 'Execute on the {0} day of every month at {1}:{2}',
perWeekHelper: 'Execute every week on {0} at {1}:{2}',
perDayHelper: 'Execute every day at {0}:{1}',
perHourHelper: 'Execute every hour at {0} minutes',
perNDayHelper: 'Execute every {0} days at {1}:{2}',
perNHourHelper: 'Execute every {0} hours at {1}',
perNMinuteHelper: 'Execute every {0} minutes',
perNSecondHelper: 'Execute every {0} seconds',
perMonth: 'Every monthly',
perWeek: 'Every week',
perHour: 'Every hour',
@ -957,8 +967,6 @@ const message = {
perNHour: 'Every N hours',
perNMinute: 'Every N minutes',
perNSecond: 'Every N seconds',
per: 'Every ',
handle: 'Handle',
day: 'Day',
monday: 'Monday',
tuesday: 'Tuesday',

View File

@ -200,6 +200,7 @@ const message = {
simpleName: '支持非底線開頭英文數字_,長度3-30',
simplePassword: '支持非底線開頭英文數字_,長度1-30',
dbName: '支持非特殊字符開頭英文中文數字.-_長度1-64',
composeName: '支持非特殊字符開頭小寫英文數字-和_長度1-256',
imageName: '支持英文數字:@/.-_,長度1-256',
volumeName: '支持英文數字.-和_,長度2-30',
supervisorName: '支援非特殊字元開頭,英文數字-和_,長度1-128',
@ -902,6 +903,14 @@ const message = {
retainCopiesUnit: ' (查看)',
cronSpecRule: ' {0} 行中執行週期格式錯誤請檢查後重試',
cronSpecRule2: '執行週期格式錯誤請檢查後重試',
perMonthHelper: '每月 {0} {1}:{2} 執行',
perWeekHelper: '每週 {0} {1}:{2} 執行',
perDayHelper: '每日 {0}:{1} 執行',
perHourHelper: '每小時 {0} 執行',
perNDayHelper: ' {0} {1}:{2} 執行',
perNHourHelper: ' {0}小時 {1} 執行',
perNMinuteHelper: ' {0} 執行',
perNSecondHelper: ' {0} 執行',
perMonth: '每月',
perWeek: '每周',
perHour: '每小時',
@ -910,8 +919,6 @@ const message = {
perNHour: ' N ',
perNMinute: ' N 分鐘',
perNSecond: ' N ',
per: '每',
handle: '執行',
day: '日',
monday: '周一',
tuesday: '周二',

View File

@ -200,6 +200,7 @@ const message = {
simpleName: '支持非下划线开头英文数字_,长度3-30',
simplePassword: '支持非下划线开头英文数字_,长度1-30',
dbName: '支持非特殊字符开头英文中文数字.-_,长度1-64',
composeName: '支持非特殊字符开头小写英文数字-和_,长度1-256',
imageName: '支持英文数字:@/.-_,长度1-256',
volumeName: '支持英文数字.-和_,长度2-30',
supervisorName: '支持非特殊字符开头,英文数字-和_,长度1-128',
@ -902,6 +903,14 @@ const message = {
retainCopiesUnit: ' (查看)',
cronSpecRule: ' {0} 行中执行周期格式错误请检查后重试',
cronSpecRule2: '执行周期格式错误请检查后重试',
perMonthHelper: '每月 {0} {1}:{2} 执行',
perWeekHelper: '每周 {0} {1}:{2} 执行',
perDayHelper: '每日 {0}:{1} 执行',
perHourHelper: '每小时 {0} 执行',
perNDayHelper: ' {0} {1}:{2} 执行',
perNHourHelper: ' {0}小时 {1} 执行',
perNMinuteHelper: ' {0} 执行',
perNSecondHelper: ' {0} 执行',
perMonth: '每月',
perWeek: '每周',
perHour: '每小时',
@ -910,8 +919,6 @@ const message = {
perNHour: ' N ',
perNMinute: ' N 分钟',
perNSecond: ' N ',
per: '每',
handle: '执行',
day: '日',
monday: '周一',
tuesday: '周二',

View File

@ -98,7 +98,7 @@ const form = reactive({
envFileContent: `env_file:\n - 1panel.env`,
});
const rules = reactive({
name: [Rules.requiredInput, Rules.imageName],
name: [Rules.requiredInput, Rules.composeName],
path: [Rules.requiredInput],
template: [Rules.requiredSelect],
});

View File

@ -373,7 +373,7 @@
/>
<fu-table-operations
fix
width="180px"
width="200px"
:ellipsis="2"
:buttons="buttons"
:label="$t('commons.table.operate')"

View File

@ -517,18 +517,17 @@ const search = async () => {
form.cmd = res.data.cmd || [];
for (const item of form.cmd) {
if (item.indexOf(' ') !== -1) {
itemCmd += `"${item.replaceAll('"', '\\"')}" `;
itemCmd += `"${escapeQuotes(item)}" `;
} else {
itemCmd += item + ' ';
}
}
form.cmdStr = itemCmd.trimEnd();
let itemEntrypoint = '';
form.entrypoint = res.data.entrypoint || [];
for (const item of form.entrypoint) {
if (item.indexOf(' ') !== -1) {
itemEntrypoint += `"${item.replaceAll('"', '\\"')}" `;
itemEntrypoint += `"${escapeQuotes(item)}" `;
} else {
itemEntrypoint += item + ' ';
}
@ -656,14 +655,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
const submit = async () => {
form.cmd = [];
if (form.cmdStr) {
let itemCmd = splitWithQuotes(form.cmdStr);
let itemCmd = splitStringIgnoringQuotes(form.cmdStr);
for (const item of itemCmd) {
form.cmd.push(item.replace(/(?<!\\)"/g, '').replaceAll('\\"', '"'));
}
}
form.entrypoint = [];
if (form.entrypointStr) {
let itemEntrypoint = splitWithQuotes(form.entrypointStr);
let itemEntrypoint = splitStringIgnoringQuotes(form.entrypointStr);
for (const item of itemEntrypoint) {
form.entrypoint.push(item.replace(/(?<!\\)"/g, '').replaceAll('\\"', '"'));
}
@ -778,15 +777,25 @@ const isFromApp = (rowData: Container.ContainerHelper) => {
return false;
};
const splitWithQuotes = (str) => {
str = str.replace(/\\"/g, '<quota>');
const regex = /(?=(?:[^'"]|['"][^'"]*['"])*$)\s+/g;
let parts = str.split(regex).filter(Boolean);
let returnList = [];
for (const item of parts) {
returnList.push(item.replaceAll('<quota>', '\\"'));
const escapeQuotes = (input) => {
return input.replace(/(?<!\\)"/g, '\\"');
};
const splitStringIgnoringQuotes = (input) => {
input = input.replace(/\\"/g, '<quota>');
const regex = /"([^"]*)"|(\S+)/g;
const result = [];
let match;
while ((match = regex.exec(input)) !== null) {
if (match[1]) {
result.push(match[1].replaceAll('<quota>', '\\"'));
} else if (match[2]) {
result.push(match[2].replaceAll('<quota>', '\\"'));
}
}
return returnList;
return result;
};
onMounted(() => {

View File

@ -67,7 +67,6 @@ const loading = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'backup-cronjob-page-size',
currentPage: 1,
pageSize: 10,
total: 0,

View File

@ -166,50 +166,26 @@ export function transSpecToObj(spec: string) {
export function transSpecToStr(spec: string): string {
const specObj = transSpecToObj(spec);
let str = '';
if (specObj.specType.indexOf('N') === -1 || specObj.specType === 'perWeek') {
str += i18n.global.t('cronjob.' + specObj.specType) + ' ';
} else {
str += i18n.global.t('cronjob.per') + ' ';
}
switch (specObj.specType) {
case 'perMonth':
str +=
specObj.day +
i18n.global.t('cronjob.day') +
' ' +
loadZero(specObj.hour) +
':' +
loadZero(specObj.minute);
break;
return i18n.global.t('cronjob.perMonthHelper', [specObj.day, specObj.hour, loadZero(specObj.minute)]);
case 'perWeek':
str += loadWeek(specObj.week) + ' ' + loadZero(specObj.hour) + ':' + loadZero(specObj.minute);
break;
return i18n.global.t('cronjob.perWeekHelper', [
loadWeek(specObj.week),
specObj.hour,
loadZero(specObj.minute),
]);
case 'perDay':
str += loadZero(specObj.hour) + ':' + loadZero(specObj.minute);
break;
case 'perNDay':
str +=
specObj.day +
i18n.global.t('commons.units.day') +
', ' +
loadZero(specObj.hour) +
':' +
loadZero(specObj.minute);
break;
case 'perNHour':
str += specObj.hour + i18n.global.t('commons.units.hour') + ', ' + loadZero(specObj.minute);
break;
return i18n.global.t('cronjob.perDayHelper', [specObj.hour, loadZero(specObj.minute)]);
case 'perHour':
str += loadZero(specObj.minute);
break;
return i18n.global.t('cronjob.perHourHelper', [loadZero(specObj.minute)]);
case 'perNDay':
return i18n.global.t('cronjob.perNDayHelper', [specObj.day, specObj.hour, loadZero(specObj.minute)]);
case 'perNHour':
return i18n.global.t('cronjob.perNHourHelper', [specObj.hour, loadZero(specObj.minute)]);
case 'perNMinute':
str += loadZero(specObj.minute) + i18n.global.t('commons.units.minute');
break;
return i18n.global.t('cronjob.perNMinuteHelper', [loadZero(specObj.minute)]);
case 'perNSecond':
str += loadZero(specObj.second) + i18n.global.t('commons.units.second');
break;
return i18n.global.t('cronjob.perNSecondHelper', [loadZero(specObj.second)]);
}
return str + ' ' + i18n.global.t('cronjob.handle');
}

View File

@ -102,7 +102,7 @@ const bindRef = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'backup-page-size',
cacheSizeKey: 'license-page-size',
currentPage: 1,
pageSize: 10,
total: 0,