diff --git a/backend/app/service/docker.go b/backend/app/service/docker.go index 2ff3adb4a..b662bce81 100644 --- a/backend/app/service/docker.go +++ b/backend/app/service/docker.go @@ -198,6 +198,23 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error { daemonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"} } } + case "http-proxy", "https-proxy": + delete(daemonMap, "proxies") + if len(req.Value) > 0 { + proxies := map[string]interface{}{ + req.Key: req.Value, + } + daemonMap["proxies"] = proxies + } + case "socks5-proxy": + delete(daemonMap, "proxies") + if len(req.Value) > 0 { + proxies := map[string]interface{}{ + "http-proxy": req.Value, + "https-proxy": req.Value, + } + daemonMap["proxies"] = proxies + } } if len(daemonMap) == 0 { _ = os.Remove(constant.DaemonJsonPath) diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 5e12d4238..e87a2b145 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1392,11 +1392,14 @@ const message = { proxyHelper: 'After setting up the proxy server, it will be effective in the following scenarios:', proxyHelper1: 'Downloading and synchronizing installation packages from the app store (Professional)', proxyHelper2: 'System version upgrades and retrieving update information (Professional)', + proxyHelper4: 'Docker Daemon provides network access proxy (Professional)', proxyHelper3: 'Verification and synchronization of system licenses', proxyType: 'Proxy Type', proxyUrl: 'Proxy Address', proxyPort: 'Proxy Port', proxyPasswdKeep: 'Remember Password', + proxyDocker: 'Docker Proxy', + proxyDockerHelper: 'Docker Daemon provides network access proxy', systemIPWarning: 'The server address is not currently set. Please set it in the control panel first!', systemIPWarning1: 'The current server address is set to {0}, and quick redirection is not possible!', defaultNetwork: 'Network Card', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 5b646bed3..260eeb62d 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1314,11 +1314,14 @@ const message = { proxyHelper: '設置代理伺服器後,將在以下場景中生效:', proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)', proxyHelper2: '系統版本升級及獲取更新說明(專業版功能)', + proxyHelper4: 'Docker Daemon 提供網絡訪問代理(專業版功能)', proxyHelper3: '系統許可證的驗證和同步', proxyType: '代理類型', proxyUrl: '代理地址', proxyPort: '代理端口', proxyPasswdKeep: '記住密碼', + proxyDocker: 'Docker 代理', + proxyDockerHelper: '為 Docker Daemon 提供網絡訪問代理', systemIPWarning: '當前未設置服務器地址,請先在面板設置中設置!', systemIPWarning1: '當前服務器地址設置為 {0},無法快速跳轉!', defaultNetwork: '默認網卡', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index dd0bbf18d..fa12e84e1 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1317,10 +1317,13 @@ const message = { proxyHelper1: '应用商店的安装包下载和同步(专业版功能)', proxyHelper2: '系统版本升级及获取更新说明(专业版功能)', proxyHelper3: '系统许可证的验证和同步', + proxyHelper4: 'Docker Daemon 提供网络访问代理(专业版功能)', proxyType: '代理类型', proxyUrl: '代理地址', proxyPort: '代理端口', proxyPasswdKeep: '记住密码', + proxyDocker: 'Docker 代理', + proxyDockerHelper: '为 Docker Daemon 提供网络访问代理', systemIPWarning: '当前未设置服务器地址,请先在面板设置中设置!', systemIPWarning1: '当前服务器地址设置为 {0},无法快速跳转!', defaultNetwork: '默认网卡', diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 655e36027..a7b070c86 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -589,3 +589,29 @@ export const getFileType = (extension: string) => { }); return type; }; + +export const escapeProxyURL = (url: string): string => { + const encodeMap: { [key: string]: string } = { + ':': '%%3A', + '/': '%%2F', + '?': '%%3F', + '#': '%%23', + '[': '%%5B', + ']': '%%5D', + '@': '%%40', + '!': '%%21', + $: '%%24', + '&': '%%26', + "'": '%%27', + '(': '%%28', + ')': '%%29', + '*': '%%2A', + '+': '%%2B', + ',': '%%2C', + ';': '%%3B', + '=': '%%3D', + '%': '%%25', + }; + + return url.replace(/[\/:?#[\]@!$&'()*+,;=%~]/g, (match) => encodeMap[match] || match); +}; diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index c77cd8baf..748c2a38e 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -239,6 +239,7 @@ const form = reactive({ proxyUser: '', proxyPasswd: '', proxyPasswdKeep: '', + proxyDocker: '', proHideMenus: ref(i18n.t('setting.unSetting')), hideMenuList: '', @@ -302,6 +303,7 @@ const search = async () => { form.theme = xpackRes.data.theme || globalStore.themeConfig.theme; form.themeColor = JSON.parse(xpackRes.data.themeColor); globalStore.themeConfig.themeColor = xpackRes.data.themeColor; + form.proxyDocker = xpackRes.data.proxyDocker; } } else { form.theme = res.data.theme; @@ -355,6 +357,7 @@ const onChangeProxy = () => { user: form.proxyUser, passwd: form.proxyPasswd, passwdKeep: form.proxyPasswdKeep, + proxyDocker: form.proxyDocker, }); }; const onChangeNetwork = () => { diff --git a/frontend/src/views/setting/panel/proxy/index.vue b/frontend/src/views/setting/panel/proxy/index.vue index d379a07c2..ffc46069c 100644 --- a/frontend/src/views/setting/panel/proxy/index.vue +++ b/frontend/src/views/setting/panel/proxy/index.vue @@ -19,6 +19,7 @@ @@ -59,6 +60,10 @@ :label="$t('setting.proxyPasswdKeep')" /> + + + {{ $t('setting.proxyDockerHelper') }} + @@ -74,6 +79,8 @@ + + @@ -87,11 +94,16 @@ import DrawerHeader from '@/components/drawer-header/index.vue'; import { updateProxy } from '@/api/modules/setting'; import { GlobalStore } from '@/store'; import { storeToRefs } from 'pinia'; +import { updateXpackSettingByKey } from '@/utils/xpack'; +import { updateDaemonJson } from '@/api/modules/container'; +import ConfirmDialog from '@/components/confirm-dialog/index.vue'; +import { escapeProxyURL } from '@/utils/util'; const globalStore = GlobalStore(); const emit = defineEmits<{ (e: 'search'): void }>(); const { isProductPro } = storeToRefs(globalStore); +const confirmDialogRef = ref(); const formRef = ref(); const rules = reactive({ proxyType: [Rules.requiredSelect], @@ -101,6 +113,7 @@ const rules = reactive({ const loading = ref(false); const passwordVisible = ref(false); +const proxyDockerVisible = ref(false); const form = reactive({ proxyUrl: '', proxyType: '', @@ -110,6 +123,7 @@ const form = reactive({ proxyPasswd: '', proxyPasswdKeep: '', proxyPasswdKeepItem: false, + proxyDocker: false, }); interface DialogProps { @@ -119,6 +133,7 @@ interface DialogProps { user: string; passwd: string; passwdKeep: string; + proxyDocker: string; } const acceptParams = (params: DialogProps): void => { if (params.url) { @@ -134,6 +149,8 @@ const acceptParams = (params: DialogProps): void => { form.proxyPortItem = params.port ? Number(params.port) : 7890; form.proxyUser = params.user; form.proxyPasswd = params.passwd; + form.proxyDocker = params.proxyDocker !== ''; + proxyDockerVisible.value = params.proxyDocker !== ''; passwordVisible.value = true; form.proxyPasswdKeepItem = params.passwdKeep === 'Enable'; }; @@ -150,6 +167,7 @@ const submitChangePassword = async (formEl: FormInstance | undefined) => { proxyUser: isClose ? '' : form.proxyUser, proxyPasswd: isClose ? '' : form.proxyPasswd, proxyPasswdKeep: '', + proxyDocker: isClose ? false : form.proxyDocker, }; if (!isClose) { params.proxyPasswdKeep = form.proxyPasswdKeepItem ? 'Enable' : 'Disable'; @@ -157,19 +175,78 @@ const submitChangePassword = async (formEl: FormInstance | undefined) => { if (form.proxyType === 'http' || form.proxyType === 'https') { params.proxyUrl = form.proxyType + '://' + form.proxyUrl; } - loading.value = true; - await updateProxy(params) - .then(async () => { - loading.value = false; - emit('search'); - passwordVisible.value = false; - MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - }) - .catch(() => { - loading.value = false; - }); + if ( + isProductPro.value && + (params.proxyDocker || + (proxyDockerVisible.value && isClose) || + (proxyDockerVisible.value && !isClose) || + (proxyDockerVisible.value && !params.proxyDocker)) + ) { + let confirmParams = { + header: i18n.global.t('database.confChange'), + operationInfo: i18n.global.t('database.restartNowHelper'), + submitInputInfo: i18n.global.t('database.restartNow'), + }; + confirmDialogRef.value!.acceptParams(confirmParams); + } else { + loading.value = true; + await updateProxy(params) + .then(async () => { + loading.value = false; + emit('search'); + passwordVisible.value = false; + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + }) + .catch(() => { + loading.value = false; + }); + } }); }; + +const onSubmit = async () => { + try { + loading.value = true; + let isClose = form.proxyType === '' || form.proxyType === 'close'; + let params = { + proxyType: isClose ? '' : form.proxyType, + proxyUrl: isClose ? '' : form.proxyUrl, + proxyPort: isClose ? '' : form.proxyPortItem + '', + proxyUser: isClose ? '' : form.proxyUser, + proxyPasswd: isClose ? '' : form.proxyPasswd, + proxyPasswdKeep: '', + proxyDocker: isClose ? false : form.proxyDocker, + }; + if (!isClose) { + params.proxyPasswdKeep = form.proxyPasswdKeepItem ? 'Enable' : 'Disable'; + } + let proxyPort = params.proxyPort ? `:${params.proxyPort}` : ''; + let proxyUser = params.proxyUser ? `${escapeProxyURL(params.proxyUser)}` : ''; + let proxyPasswd = ''; + if (params.proxyUser) { + proxyPasswd = params.proxyPasswd ? `:${escapeProxyURL(params.proxyPasswd)}@` : '@'; + } + + let proxyUrl = form.proxyType + '://' + proxyUser + proxyPasswd + form.proxyUrl + proxyPort; + if (form.proxyType === 'http' || form.proxyType === 'https') { + params.proxyUrl = form.proxyType + '://' + form.proxyUrl; + } + await updateProxy(params); + if (isClose || params.proxyDocker === false) { + params.proxyUrl = ''; + } + await updateXpackSettingByKey('ProxyDocker', proxyUrl); + await updateDaemonJson(`${form.proxyType}-proxy`, proxyUrl); + emit('search'); + handleClose(); + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + } catch (error) { + console.error(error); + } finally { + loading.value = false; + } +}; + const handleClose = () => { passwordVisible.value = false; };