mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-19 22:09:03 +08:00
feat: 增加系统 IP 设置,实现容器端口跳转功能 (#1479)
This commit is contained in:
parent
847c14ddda
commit
64f80a95ab
12 changed files with 156 additions and 10 deletions
|
|
@ -5,6 +5,7 @@ import "time"
|
||||||
type SettingInfo struct {
|
type SettingInfo struct {
|
||||||
UserName string `json:"userName"`
|
UserName string `json:"userName"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
SystemIP string `json:"systemIP"`
|
||||||
SystemVersion string `json:"systemVersion"`
|
SystemVersion string `json:"systemVersion"`
|
||||||
|
|
||||||
SessionTimeout string `json:"sessionTimeout"`
|
SessionTimeout string `json:"sessionTimeout"`
|
||||||
|
|
|
||||||
|
|
@ -407,6 +407,9 @@ var AddMfaInterval = &gormigrate.Migration{
|
||||||
if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil {
|
if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "SystemIP", Value: ""}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export namespace Setting {
|
||||||
userName: string;
|
userName: string;
|
||||||
password: string;
|
password: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
systemIP: string;
|
||||||
systemVersion: string;
|
systemVersion: string;
|
||||||
|
|
||||||
sessionTimeout: number;
|
sessionTimeout: number;
|
||||||
|
|
|
||||||
|
|
@ -969,6 +969,8 @@ const message = {
|
||||||
sessionTimeoutError: 'The minimum timeout is 300 seconds',
|
sessionTimeoutError: 'The minimum timeout is 300 seconds',
|
||||||
sessionTimeoutHelper:
|
sessionTimeoutHelper:
|
||||||
'If you do not operate the panel for more than {0} seconds, the panel automatically logs out',
|
'If you do not operate the panel for more than {0} seconds, the panel automatically logs out',
|
||||||
|
systemIP: 'System IP',
|
||||||
|
systemIPWarning: 'Please set the system IP in the panel settings first.',
|
||||||
syncTime: 'Server time',
|
syncTime: 'Server time',
|
||||||
timeZone: 'Time Zone',
|
timeZone: 'Time Zone',
|
||||||
timeZoneChangeHelper: 'Changing the time zone requires restarting the service. Do you want to continue?',
|
timeZoneChangeHelper: 'Changing the time zone requires restarting the service. Do you want to continue?',
|
||||||
|
|
|
||||||
|
|
@ -949,6 +949,8 @@ const message = {
|
||||||
sessionTimeout: '超时时间',
|
sessionTimeout: '超时时间',
|
||||||
sessionTimeoutError: '最小超时时间为 300 秒',
|
sessionTimeoutError: '最小超时时间为 300 秒',
|
||||||
sessionTimeoutHelper: '如果用户超过 {0} 秒未操作面板,面板将自动退出登录',
|
sessionTimeoutHelper: '如果用户超过 {0} 秒未操作面板,面板将自动退出登录',
|
||||||
|
systemIP: '服务器 IP',
|
||||||
|
systemIPWarning: '请先在面板设置中设置服务器 IP',
|
||||||
syncTime: '服务器时间',
|
syncTime: '服务器时间',
|
||||||
timeZone: '系统时区',
|
timeZone: '系统时区',
|
||||||
timeZoneChangeHelper: '系统时区修改需要重启服务,是否继续?',
|
timeZoneChangeHelper: '系统时区修改需要重启服务,是否继续?',
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,16 @@
|
||||||
<div v-if="row.ports">
|
<div v-if="row.ports">
|
||||||
<div v-for="(item, index) in row.ports" :key="index">
|
<div v-for="(item, index) in row.ports" :key="index">
|
||||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||||
<el-tag class="tagMargin">{{ item }}</el-tag>
|
<el-button
|
||||||
|
@click="goDashboard(item)"
|
||||||
|
class="tagMargin"
|
||||||
|
icon="Position"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!row.expand && row.ports.length > 3">
|
<div v-if="!row.expand && row.ports.length > 3">
|
||||||
|
|
@ -154,8 +163,9 @@ import { Container } from '@/api/interface/container';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { computeSize } from '@/utils/util';
|
import { computeSize } from '@/utils/util';
|
||||||
|
import { getSettingInfo } from '@/api/modules/setting';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const data = ref();
|
const data = ref();
|
||||||
|
|
@ -184,6 +194,20 @@ const loadStatus = async () => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goDashboard = async (port: any) => {
|
||||||
|
if (!port || port.indexOf(':') === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let portEx = port.split(':')[0];
|
||||||
|
const res = await getSettingInfo();
|
||||||
|
if (!res.data.systemIP) {
|
||||||
|
MsgError(i18n.global.t('setting.systemIPWarning'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(`http://${res.data.systemIP}:${portEx}`, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
const goSetting = async () => {
|
const goSetting = async () => {
|
||||||
router.push({ name: 'ContainerSetting' });
|
router.push({ name: 'ContainerSetting' });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,6 @@ const acceptParams = (params: DialogProps): void => {
|
||||||
item.host = item.hostPort;
|
item.host = item.hostPort;
|
||||||
}
|
}
|
||||||
dialogData.value.rowData.volumes = dialogData.value.rowData.volumes || [];
|
dialogData.value.rowData.volumes = dialogData.value.rowData.volumes || [];
|
||||||
console.log(dialogData.value.rowData.cpuShares);
|
|
||||||
}
|
}
|
||||||
loadLimit();
|
loadLimit();
|
||||||
loadImageOptions();
|
loadImageOptions();
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ import { GetAppPort } from '@/api/modules/app';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import useClipboard from 'vue-clipboard3';
|
import useClipboard from 'vue-clipboard3';
|
||||||
|
import { getSettingInfo } from '@/api/modules/setting';
|
||||||
const { toClipboard } = useClipboard();
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
@ -261,9 +262,12 @@ const goDashboard = async () => {
|
||||||
phpVisiable.value = true;
|
phpVisiable.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let href = window.location.href;
|
const res = await getSettingInfo();
|
||||||
let ipLocal = href.split('//')[1].split(':')[0];
|
if (!res.data.systemIP) {
|
||||||
window.open(`http://${ipLocal}:${phpadminPort.value}`, '_blank');
|
MsgError(i18n.global.t('setting.systemIPWarning'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(`http://${res.data.systemIP}:${phpadminPort.value}`, '_blank');
|
||||||
};
|
};
|
||||||
const getAppDetail = (key: string) => {
|
const getAppDetail = (key: string) => {
|
||||||
router.push({ name: 'AppDetail', params: { appKey: key } });
|
router.push({ name: 'AppDetail', params: { appKey: key } });
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,9 @@ import { nextTick, onBeforeUnmount, ref } from 'vue';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
import { GetAppPort } from '@/api/modules/app';
|
import { GetAppPort } from '@/api/modules/app';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
|
import { MsgError } from '@/utils/message';
|
||||||
|
import { getSettingInfo } from '@/api/modules/setting';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const maskShow = ref(true);
|
const maskShow = ref(true);
|
||||||
|
|
@ -94,9 +97,12 @@ const goDashboard = async () => {
|
||||||
commandVisiable.value = true;
|
commandVisiable.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let href = window.location.href;
|
const res = await getSettingInfo();
|
||||||
let ipLocal = href.split('//')[1].split(':')[0];
|
if (!res.data.systemIP) {
|
||||||
window.open(`http://${ipLocal}:${redisCommandPort.value}`, '_blank');
|
MsgError(i18n.global.t('setting.systemIPWarning'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(`http://${res.data.systemIP}:${redisCommandPort.value}`, '_blank');
|
||||||
};
|
};
|
||||||
const getAppDetail = (key: string) => {
|
const getAppDetail = (key: string) => {
|
||||||
router.push({ name: 'AppDetail', params: { appKey: key } });
|
router.push({ name: 'AppDetail', params: { appKey: key } });
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const hadleSuccess: UploadProps['onSuccess'] = (res, file) => {
|
const hadleSuccess: UploadProps['onSuccess'] = (res, file) => {
|
||||||
console.log(file.name);
|
|
||||||
file.status = 'success';
|
file.status = 'success';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,22 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.systemIP')" prop="systemIP">
|
||||||
|
<el-input disabled v-if="form.systemIP" v-model="form.systemIP">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeSystemIP" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input disabled v-if="!form.systemIP" v-model="unset">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeSystemIP" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.syncTime')">
|
<el-form-item :label="$t('setting.syncTime')">
|
||||||
<el-input disabled v-model="form.localTime">
|
<el-input disabled v-model="form.localTime">
|
||||||
<template #append>
|
<template #append>
|
||||||
|
|
@ -100,6 +116,7 @@
|
||||||
<Password ref="passwordRef" />
|
<Password ref="passwordRef" />
|
||||||
<UserName ref="userNameRef" />
|
<UserName ref="userNameRef" />
|
||||||
<PanelName ref="panelNameRef" @search="search()" />
|
<PanelName ref="panelNameRef" @search="search()" />
|
||||||
|
<SystemIP ref="systemIPRef" @search="search()" />
|
||||||
<Timeout ref="timeoutRef" @search="search()" />
|
<Timeout ref="timeoutRef" @search="search()" />
|
||||||
<TimeZone ref="timezoneRef" @search="search()" />
|
<TimeZone ref="timezoneRef" @search="search()" />
|
||||||
<Ntp ref="ntpRef" @search="search()" />
|
<Ntp ref="ntpRef" @search="search()" />
|
||||||
|
|
@ -118,6 +135,7 @@ import Password from '@/views/setting/panel/password/index.vue';
|
||||||
import UserName from '@/views/setting/panel/username/index.vue';
|
import UserName from '@/views/setting/panel/username/index.vue';
|
||||||
import Timeout from '@/views/setting/panel/timeout/index.vue';
|
import Timeout from '@/views/setting/panel/timeout/index.vue';
|
||||||
import PanelName from '@/views/setting/panel/name/index.vue';
|
import PanelName from '@/views/setting/panel/name/index.vue';
|
||||||
|
import SystemIP from '@/views/setting/panel/systemip/index.vue';
|
||||||
import TimeZone from '@/views/setting/panel/timezone/index.vue';
|
import TimeZone from '@/views/setting/panel/timezone/index.vue';
|
||||||
import Ntp from '@/views/setting/panel/ntp/index.vue';
|
import Ntp from '@/views/setting/panel/ntp/index.vue';
|
||||||
|
|
||||||
|
|
@ -136,6 +154,7 @@ const form = reactive({
|
||||||
timeZone: '',
|
timeZone: '',
|
||||||
ntpSite: '',
|
ntpSite: '',
|
||||||
panelName: '',
|
panelName: '',
|
||||||
|
systemIP: '',
|
||||||
theme: '',
|
theme: '',
|
||||||
language: '',
|
language: '',
|
||||||
complexityVerification: '',
|
complexityVerification: '',
|
||||||
|
|
@ -146,9 +165,11 @@ const show = ref();
|
||||||
const userNameRef = ref();
|
const userNameRef = ref();
|
||||||
const passwordRef = ref();
|
const passwordRef = ref();
|
||||||
const panelNameRef = ref();
|
const panelNameRef = ref();
|
||||||
|
const systemIPRef = ref();
|
||||||
const timeoutRef = ref();
|
const timeoutRef = ref();
|
||||||
const ntpRef = ref();
|
const ntpRef = ref();
|
||||||
const timezoneRef = ref();
|
const timezoneRef = ref();
|
||||||
|
const unset = ref(i18n.t('setting.unSetting'));
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
const res = await getSettingInfo();
|
const res = await getSettingInfo();
|
||||||
|
|
@ -159,6 +180,7 @@ const search = async () => {
|
||||||
form.timeZone = res.data.timeZone;
|
form.timeZone = res.data.timeZone;
|
||||||
form.ntpSite = res.data.ntpSite;
|
form.ntpSite = res.data.ntpSite;
|
||||||
form.panelName = res.data.panelName;
|
form.panelName = res.data.panelName;
|
||||||
|
form.systemIP = res.data.systemIP;
|
||||||
form.theme = res.data.theme;
|
form.theme = res.data.theme;
|
||||||
form.language = res.data.language;
|
form.language = res.data.language;
|
||||||
form.complexityVerification = res.data.complexityVerification;
|
form.complexityVerification = res.data.complexityVerification;
|
||||||
|
|
@ -176,6 +198,9 @@ const onChangeTitle = () => {
|
||||||
const onChangeTimeout = () => {
|
const onChangeTimeout = () => {
|
||||||
timeoutRef.value.acceptParams({ sessionTimeout: form.sessionTimeout });
|
timeoutRef.value.acceptParams({ sessionTimeout: form.sessionTimeout });
|
||||||
};
|
};
|
||||||
|
const onChangeSystemIP = () => {
|
||||||
|
systemIPRef.value.acceptParams({ systemIP: form.systemIP });
|
||||||
|
};
|
||||||
const onChangeTimeZone = () => {
|
const onChangeTimeZone = () => {
|
||||||
timezoneRef.value.acceptParams({ timeZone: form.timeZone });
|
timezoneRef.value.acceptParams({ timeZone: form.timeZone });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
80
frontend/src/views/setting/panel/systemip/index.vue
Normal file
80
frontend/src/views/setting/panel/systemip/index.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('setting.systemIP')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('setting.systemIP')" prop="systemIP" :rules="Rules.ip">
|
||||||
|
<el-input clearable v-model="form.systemIP" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSaveSystemIP(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { updateSetting } from '@/api/modules/setting';
|
||||||
|
import { FormInstance } from 'element-plus';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
systemIP: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
systemIP: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.systemIP = params.systemIP;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveSystemIP = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
await updateSetting({ key: 'SystemIP', value: form.systemIP })
|
||||||
|
.then(async () => {
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
loading.value = false;
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
emit('search');
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Loading…
Add table
Reference in a new issue