feat: 面板代理服务器提供 docker 代理 (#6986)

This commit is contained in:
2024-11-08 18:19:48 +08:00 committed by GitHub
parent cccbaafd1b
commit 204f9f1fbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 143 additions and 11 deletions

View File

@ -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)

View File

@ -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',

View File

@ -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: '默認網卡',

View File

@ -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: '默认网卡',

View File

@ -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);
};

View File

@ -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 = () => {

View File

@ -19,6 +19,7 @@
<ul style="margin-left: -20px">
<li v-if="isProductPro">{{ $t('setting.proxyHelper1') }}</li>
<li v-if="isProductPro">{{ $t('setting.proxyHelper2') }}</li>
<li v-if="isProductPro">{{ $t('setting.proxyHelper4') }}</li>
<li>{{ $t('setting.proxyHelper3') }}</li>
</ul>
</template>
@ -59,6 +60,10 @@
:label="$t('setting.proxyPasswdKeep')"
/>
</el-form-item>
<el-form-item v-if="isProductPro">
<el-checkbox v-model="form.proxyDocker" :label="$t('setting.proxyDocker')" />
<span class="input-help">{{ $t('setting.proxyDockerHelper') }}</span>
</el-form-item>
</div>
</el-col>
</el-row>
@ -74,6 +79,8 @@
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit" />
</div>
</template>
@ -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<FormInstance>();
const rules = reactive({
proxyType: [Rules.requiredSelect],
@ -101,6 +113,7 @@ const rules = reactive({
const loading = ref(false);
const passwordVisible = ref<boolean>(false);
const proxyDockerVisible = ref<boolean>(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;
};