feat: add docs source config and runtime env linkage (#12182)

This commit is contained in:
ssongliu
2026-03-16 08:13:51 +00:00
committed by GitHub
parent 2dfe5096f0
commit 2f3ad3537e
19 changed files with 252 additions and 54 deletions
+1
View File
@@ -19,6 +19,7 @@ type SettingInfo struct {
Theme string `json:"theme"`
MenuTabs string `json:"menuTabs"`
Language string `json:"language"`
DocSource string `json:"docSource"`
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
+15 -3
View File
@@ -286,10 +286,22 @@ type noteDetailHelper struct {
}
func (u *UpgradeService) LoadRelease() ([]dto.ReleasesNotes, error) {
docSource, _ := settingRepo.GetValueByKey("DocSource")
lang, _ := settingRepo.GetValueByKey("Language")
var notes []dto.ReleasesNotes
url := "https://docs.1panel.pro/v2/search/search_index.json"
if global.CONF.Base.Edition != "intl" {
url = "https://1panel.cn/docs/v2/search/search_index.json"
url := "https://1panel.cn/docs/v2/search/search_index.json"
useIntlDocs := false
lang = strings.ToLower(strings.TrimSpace(lang))
if docSource == "withByRegion" {
useIntlDocs = global.CONF.Base.Edition == "intl"
if !useIntlDocs && lang != "zh" {
useIntlDocs = true
}
} else {
useIntlDocs = lang != "zh"
}
if useIntlDocs {
url = "https://docs.1panel.pro/v2/search/search_index.json"
}
resp, err := req_helper.HandleGet(url)
if err != nil {
+1
View File
@@ -34,6 +34,7 @@ func Init() {
migrations.AddDashboardCarouselSetting,
migrations.AddEditionSetting,
migrations.UpdateAiLocalModelMenuTitle,
migrations.AddDocSourceSetting,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+20
View File
@@ -70,6 +70,9 @@ var InitSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "Language", Value: language}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "DocSource", Value: "withByRegion"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SessionTimeout", Value: "86400"}).Error; err != nil {
return err
}
@@ -835,6 +838,23 @@ var AddEditionSetting = &gormigrate.Migration{
},
}
var AddDocSourceSetting = &gormigrate.Migration{
ID: "20260316-add-doc-source-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "DocSource").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "DocSource", Value: "withByRegion"}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "DocSource").Update("value", "withByRegion").Error
}
return nil
},
}
var UpdateAiLocalModelMenuTitle = &gormigrate.Migration{
ID: "20260307-update-ai-local-model-menu-title",
Migrate: func(tx *gorm.DB) error {
+1
View File
@@ -21,6 +21,7 @@ export namespace Setting {
theme: string;
menuTabs: string;
language: string;
docSource: string;
defaultIO: string;
defaultNetwork: string;
lastCleanTime: string;
+6
View File
@@ -1811,6 +1811,10 @@ const message = {
light: 'Light',
auto: 'Follow System',
language: 'Language',
runtimeEnv: 'Runtime environment',
docSource: 'Document source',
withByRegion: 'Follow region (Default)',
withByLang: 'Follow system language',
region: 'Region',
cn: 'Mainland China',
intl: 'Global',
@@ -1819,6 +1823,8 @@ const message = {
regionHelper2: 'App Store and script library',
regionHelper3: 'User manual and related documents',
regionHelper4: 'This may affect future downloads and access. Proceed with caution.',
regionTip: 'Region affects the system update source and package download address.',
docSourceTip: 'Document source determines the language used for docs and release note links.',
languageHelper:
'By default, it follows the browser language. This parameter takes effect only on the current browser',
sessionTimeout: 'Session timeout',
+7
View File
@@ -1842,6 +1842,10 @@ const message = {
light: 'Claro',
auto: 'Seguir sistema',
language: 'Idioma',
runtimeEnv: 'Entorno de ejecución',
docSource: 'Fuente de documentación',
withByRegion: 'Seguir región de operación (Predeterminado)',
withByLang: 'Seguir idioma del sistema',
region: 'Región de operación',
cn: 'China continental',
intl: 'Global',
@@ -1850,6 +1854,9 @@ const message = {
regionHelper2: 'Tienda de aplicaciones y biblioteca de scripts',
regionHelper3: 'Manual de usuario y documentación relacionada',
regionHelper4: 'Esto puede afectar futuras descargas y accesos. Proceda con precaución.',
regionTip:
'La región de operación afecta la fuente de actualización del sistema y la dirección de descarga de paquetes de instalación.',
docSourceTip: 'La fuente de documentación determina el idioma de los enlaces de documentación y registro de actualizaciones.',
languageHelper:
'Por defecto sigue el idioma del navegador. Este parámetro solo tiene efecto en el navegador actual',
sessionTimeout: 'Tiempo de espera de sesión',
+6
View File
@@ -1818,6 +1818,10 @@ const message = {
light: 'ライト',
auto: 'システムをフォローします',
language: '言語',
runtimeEnv: '実行環境',
docSource: 'ドキュメントソース',
withByRegion: '運用リージョンに従うデフォルト',
withByLang: 'システム言語に従う',
region: '運用リージョン',
cn: '中国本土',
intl: 'グローバル',
@@ -1826,6 +1830,8 @@ const message = {
regionHelper2: 'アプリストアおよびスクリプトライブラリ',
regionHelper3: 'ユーザーマニュアルおよび関連ドキュメント',
regionHelper4: '今後のダウンロードやアクセスに影響する可能性がありますご注意ください',
regionTip: '運用リージョンはシステム更新ソースとインストールパッケージのダウンロードアドレスに影響します',
docSourceTip: 'ドキュメントソースはドキュメントと更新ログの遷移言語を決定します',
languageHelper:
'デフォルトではブラウザ言語に従いますこのパラメーターは現在のブラウザでのみ有効になります',
sessionTimeout: 'セッションタイムアウト',
+6
View File
@@ -1786,6 +1786,10 @@ const message = {
light: '라이트',
auto: '시스템 따라가기',
language: '언어',
runtimeEnv: '실행 환경',
docSource: '문서 출처',
withByRegion: '운영 지역 따름(기본값)',
withByLang: '시스템 언어 따름',
region: '운영 지역',
cn: '중국 본토',
intl: '글로벌',
@@ -1794,6 +1798,8 @@ const message = {
regionHelper2: '앱 스토어 및 스크립트 라이브러리',
regionHelper3: '사용자 매뉴얼 및 관련 문서',
regionHelper4: '향후 다운로드 및 접근에 영향을 줄 수 있습니다. 주의하십시오.',
regionTip: '운영 지역은 시스템 업데이트 소스와 설치 패키지 다운로드 주소에 영향을 미칩니다.',
docSourceTip: '문서 출처는 문서와 업데이트 로그의 이동 언어를 결정합니다.',
languageHelper: '기본적으로 브라우저 언어를 따릅니다. 이 설정은 현재 브라우저에서만 적용됩니다.',
sessionTimeout: '세션 타임아웃',
sessionTimeoutError: '최소 세션 타임아웃은 300초입니다.',
+6
View File
@@ -1846,6 +1846,10 @@ const message = {
light: 'Terang',
auto: 'Ikut Sistem',
language: 'Bahasa',
runtimeEnv: 'Persekitaran operasi',
docSource: 'Sumber dokumentasi',
withByRegion: 'Ikut wilayah operasi (Lalai)',
withByLang: 'Ikut bahasa sistem',
region: 'Wilayah operasi',
cn: 'Tanah Besar China',
intl: 'Global',
@@ -1855,6 +1859,8 @@ const message = {
regionHelper3: 'Manual pengguna dan dokumen berkaitan',
regionHelper4:
'Tindakan ini mungkin menjejaskan muat turun dan akses seterusnya. Sila teruskan dengan berhati-hati.',
regionTip: 'Wilayah operasi mempengaruhi sumber kemas kini sistem serta alamat muat turun pakej pemasangan.',
docSourceTip: 'Sumber dokumentasi menentukan bahasa pautan untuk dokumentasi dan log kemas kini.',
languageHelper:
'Secara lalai, ia mengikuti bahasa penyemak imbas. Parameter ini hanya berkuat kuasa pada penyemak imbas semasa',
sessionTimeout: 'Tempoh tamat sesi',
+6
View File
@@ -1961,6 +1961,10 @@ const message = {
light: 'Claro',
auto: 'Seguir o sistema',
language: 'Idioma',
runtimeEnv: 'Ambiente de execução',
docSource: 'Fonte da documentação',
withByRegion: 'Seguir região de operação (Padrão)',
withByLang: 'Seguir idioma do sistema',
region: 'Região de operação',
cn: 'China continental',
intl: 'Global',
@@ -1969,6 +1973,8 @@ const message = {
regionHelper2: 'Loja de aplicativos e biblioteca de scripts',
regionHelper3: 'Manual do usuário e documentação relacionada',
regionHelper4: 'Isso pode afetar downloads e acessos futuros. Prossiga com cautela.',
regionTip: 'A região de operação afeta a fonte de atualização do sistema e o endereço de download dos pacotes de instalação.',
docSourceTip: 'A fonte da documentação determina o idioma de redirecionamento da documentação e do registro de atualizações.',
languageHelper: 'Por padrão, segue o idioma do navegador. Este parâmetro tem efeito apenas no navegador atual',
sessionTimeout: 'Tempo limite de sessão',
sessionTimeoutError: 'O tempo mínimo de sessão é de 300 segundos',
+6
View File
@@ -1832,6 +1832,10 @@ const message = {
light: 'Светлая',
auto: 'Как в системе',
language: 'Язык',
runtimeEnv: 'Среда выполнения',
docSource: 'Источник документации',
withByRegion: 'Следовать региону работы (по умолчанию)',
withByLang: 'Следовать языку системы',
region: 'Регион работы',
cn: 'Материковый Китай',
intl: 'Глобальный',
@@ -1840,6 +1844,8 @@ const message = {
regionHelper2: 'Магазин приложений и библиотека скриптов',
regionHelper3: 'Руководство пользователя и сопутствующая документация',
regionHelper4: 'Это может повлиять на последующие загрузки и доступ. Действуйте осторожно.',
regionTip: 'Регион работы влияет на источник обновления системы и адрес загрузки установочных пакетов.',
docSourceTip: 'Источник документации определяет язык перехода для документации и журнала обновлений.',
languageHelper: 'По умолчанию следует языку браузера. Этот параметр действует только в текущем браузере',
sessionTimeout: 'Время сессии',
sessionTimeoutError: 'Минимальное время сессии 300 секунд',
+6
View File
@@ -1839,6 +1839,10 @@ const message = {
light: 'Açık',
auto: 'Sistemi takip et',
language: 'Dil',
runtimeEnv: 'Çalışma ortamı',
docSource: 'Doküman kaynağı',
withByRegion: 'Çalışma bölgesini takip et (Varsayılan)',
withByLang: 'Sistem dilini takip et',
region: 'Çalışma bölgesi',
cn: 'Çin anakarası',
intl: 'Küresel',
@@ -1847,6 +1851,8 @@ const message = {
regionHelper2: 'Uygulama mağazası ve betik kütüphanesi',
regionHelper3: 'Kullanım kılavuzu ve ilgili dokümanlar',
regionHelper4: 'Bu işlem sonraki indirme ve erişimleri etkileyebilir. Lütfen dikkatli olun.',
regionTip: 'Çalışma bölgesi, sistem güncelleme kaynağını ve kurulum paketi indirme adresini belirgin şekilde etkiler.',
docSourceTip: 'Doküman kaynağı, dokümantasyon ve güncelleme günlüğü bağlantılarının dilini belirler.',
languageHelper:
'Varsayılan olarak tarayıcı dilini takip eder. Bu parametre yalnızca geçerli tarayıcıda etkilidir',
sessionTimeout: 'Oturum zaman aşımı',
+6
View File
@@ -1710,6 +1710,10 @@ const message = {
light: '亮色',
auto: '跟隨系統',
language: '系統語言',
runtimeEnv: '運行環境',
docSource: '文件來源',
withByRegion: '跟隨運行區域預設',
withByLang: '跟隨系統語言',
region: '運行區域',
cn: '中國大陸',
intl: '全球',
@@ -1718,6 +1722,8 @@ const message = {
regionHelper2: '應用商店與腳本庫',
regionHelper3: '使用手冊及相關文件',
regionHelper4: '此操作可能影響後續資源下載與存取請謹慎操作',
regionTip: '運行區域影響系統更新源以及安裝包下載地址',
docSourceTip: '文件來源決定文件與更新日誌的跳轉語言',
languageHelper: '預設跟隨瀏覽器語言設定後只對目前瀏覽器生效更換瀏覽器後需要重新設定',
sessionTimeout: '逾時時間',
sessionTimeoutError: '最小逾時時間為 300 ',
+7 -5
View File
@@ -1711,14 +1711,16 @@ const message = {
light: '亮色',
auto: '跟随系统',
language: '系统语言',
docSource: '文档来源',
withByRegion: '跟随运行区域默认',
withByLang: '跟随系统语言',
runtimeEnv: '运行环境',
region: '运行区域',
cn: '中国大陆',
intl: '全球',
regionHelper: '更改运行区域后将调整以下资源来源',
regionHelper1: '系统安装包',
regionHelper2: '应用商店与脚本库',
regionHelper3: '使用手册及相关文档',
regionHelper4: '该操作可能影响后续资源下载与访问请谨慎操作',
regionHelper: '该操作可能影响后续资源下载与访问是否继续',
regionTip: '运行区域影响系统更新源以及安装包下载地址',
docSourceTip: '文档来源决定文档与更新日志的跳转语言',
languageHelper: '默认跟随浏览器语言设置后只对当前浏览器生效更换浏览器后需要重新设置',
sessionTimeout: '超时时间',
sessionTimeoutError: '最小超时时间为 300 ',
+1
View File
@@ -53,6 +53,7 @@ export interface GlobalState {
isProductPro: boolean;
isIntl: boolean;
docWithRegion: boolean;
productProExpires: number;
isMasterProductPro: boolean;
isOffLine: boolean;
+12 -1
View File
@@ -4,6 +4,9 @@ import { GlobalState, ThemeConfigProp } from '../interface';
import { DeviceType } from '@/enums/app';
import i18n, { setActiveLocale } from '@/lang';
const CN_DOCS_URL = 'https://1panel.cn/docs/v2';
const INTL_DOCS_URL = 'https://docs.1panel.pro/v2';
const GlobalStore = defineStore({
id: 'GlobalState',
state: (): GlobalState => ({
@@ -47,6 +50,7 @@ const GlobalStore = defineStore({
isProductPro: false,
isIntl: false,
docWithRegion: true,
productProExpires: 0,
isMasterProductPro: false,
isOffLine: false,
@@ -60,7 +64,14 @@ const GlobalStore = defineStore({
state.themeConfig.theme === 'dark' ||
(state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches),
isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro,
docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.pro/v2' : 'https://1panel.cn/docs/v2'),
docsUrl: (state) => {
if (state.docWithRegion) {
return state.isIntl ? INTL_DOCS_URL : CN_DOCS_URL;
}
const lang = state.language.toLowerCase();
const isChinese = lang === 'zh';
return isChinese ? CN_DOCS_URL : INTL_DOCS_URL;
},
isMaster: (state) => state.currentNode === 'local',
},
actions: {
@@ -0,0 +1,120 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('setting.runtimeEnv')" @close="handleClose" size="small">
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form-item :label="$t('setting.region')" prop="edition">
<el-radio-group v-model="form.edition">
<el-radio value="cn">
<span>{{ $t('setting.cn') }}</span>
</el-radio>
<el-radio value="intl">
<span>{{ $t('setting.intl') }}</span>
</el-radio>
</el-radio-group>
<span class="input-help">{{ $t('setting.regionTip') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.docSource')" prop="docSource">
<el-radio-group v-model="form.docSource">
<el-radio value="withByRegion">
<span>{{ $t('setting.withByRegion') }}</span>
</el-radio>
<el-radio value="withByLang">
<span>{{ $t('setting.withByLang') }}</span>
</el-radio>
</el-radio-group>
<span class="input-help">{{ $t('setting.docSourceTip') }}</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSaveEdition()">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</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 { GlobalStore } from '@/store';
const globalStore = GlobalStore();
interface DialogProps {
edition: string;
docSource: string;
}
const drawerVisible = ref();
const loading = ref(false);
const form = reactive({
edition: '',
docSource: 'withByRegion',
});
const initialForm = reactive({
edition: '',
docSource: 'withByRegion',
});
const formRef = ref<FormInstance>();
const emit = defineEmits(['search']);
const acceptParams = (params: DialogProps): void => {
form.edition = params.edition;
form.docSource = params.docSource || 'withByRegion';
initialForm.edition = params.edition;
initialForm.docSource = params.docSource || 'withByRegion';
drawerVisible.value = true;
};
const onSaveEdition = async () => {
const editionChanged = form.edition !== initialForm.edition;
const docSourceChanged = form.docSource !== initialForm.docSource;
if (!editionChanged && !docSourceChanged) {
drawerVisible.value = false;
return;
}
if (editionChanged) {
try {
await ElMessageBox.confirm(i18n.global.t('setting.regionHelper'), i18n.global.t('setting.region'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
dangerouslyUseHTMLString: true,
});
} catch {
return;
}
}
loading.value = true;
try {
if (editionChanged) {
await updateSetting({ key: 'Edition', value: form.edition });
globalStore.isIntl = form.edition === 'intl';
}
if (docSourceChanged) {
await updateSetting({ key: 'DocSource', value: form.docSource });
globalStore.docWithRegion = form.docSource === 'withByRegion';
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
if (editionChanged) {
location.reload();
return;
}
emit('search');
drawerVisible.value = false;
} finally {
loading.value = false;
}
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>
+19 -45
View File
@@ -202,15 +202,10 @@
</el-button>
</el-form-item>
<el-form-item :label="$t('setting.region')" prop="edition">
<el-radio-group @change="onSaveEdition" v-model="form.edition">
<el-radio-button value="cn">
<span>{{ $t('setting.cn') }}</span>
</el-radio-button>
<el-radio-button value="intl">
<span>{{ $t('setting.intl') }}</span>
</el-radio-button>
</el-radio-group>
<el-form-item :label="$t('setting.runtimeEnv')" prop="edition">
<el-button icon="Setting" @click="onChangeRegion">
{{ runtimeEnvLabel() }}
</el-button>
</el-form-item>
</el-col>
</el-row>
@@ -228,6 +223,7 @@
<HideMenu ref="hideMenuRef" @search="search()" />
<ThemeColor ref="themeColorRef" />
<Watermark ref="watermarkRef" @search="search()" />
<Edition ref="editionRef" @search="search()" />
</div>
</template>
@@ -254,6 +250,7 @@ import PanelName from '@/views/setting/panel/name/index.vue';
import SystemIP from '@/views/setting/panel/systemip/index.vue';
import Proxy from '@/views/setting/panel/proxy/index.vue';
import HideMenu from '@/views/setting/panel/hidemenu/index.vue';
import Edition from '@/views/setting/panel/edition/index.vue';
import { storeToRefs } from 'pinia';
import { getXpackSetting, updateXpackSettingByKey } from '@/utils/xpack';
import { setPrimaryColor } from '@/utils/theme';
@@ -289,6 +286,7 @@ const form = reactive({
themeColor: {} as ThemeColor,
menuTabs: '',
language: '',
docSource: 'withByRegion',
edition: '',
complexityVerification: '',
developerMode: '',
@@ -323,6 +321,7 @@ const hideMenuRef = ref();
const watermarkRef = ref();
const themeColorRef = ref();
const apiInterfaceRef = ref();
const editionRef = ref();
const unset = ref(i18n.global.t('setting.unSetting'));
const languageOptions = ref([
@@ -353,6 +352,7 @@ const search = async () => {
form.menuTabs = res.data.menuTabs;
form.panelName = res.data.panelName;
form.language = res.data.language;
form.docSource = res.data.docSource || 'withByRegion';
form.edition = res.data.edition;
form.sessionTimeout = Number(res.data.sessionTimeout);
@@ -428,6 +428,16 @@ const onChangeHideMenus = () => {
hideMenuRef.value.acceptParams({ hideMenu: form.hideMenu });
};
const onChangeRegion = () => {
editionRef.value.acceptParams({ edition: form.edition, docSource: form.docSource });
};
const runtimeEnvLabel = () => {
const editionLabel = form.edition === 'cn' ? i18n.global.t('setting.cn') : i18n.global.t('setting.intl');
const docSourceLabel = i18n.global.t(`setting.${form.docSource || 'withByRegion'}`);
return `${editionLabel} / ${docSourceLabel}`;
};
const onChangeThemeColor = () => {
const themeColor: ThemeColor = JSON.parse(globalStore.themeConfig.themeColor);
themeColorRef.value.acceptParams({ themeColor: themeColor, theme: globalStore.themeConfig.theme });
@@ -535,9 +545,6 @@ const onSave = async (key: string, val: any) => {
await globalStore.updateLanguage(val);
location.reload();
break;
case 'Edition':
globalStore.isIntl = val === 'intl';
break;
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
@@ -548,39 +555,6 @@ const onSave = async (key: string, val: any) => {
loading.value = false;
};
const onSaveEdition = async () => {
ElMessageBox.confirm(
`
<div style="line-height: 1.6;">
<div style="margin-bottom: 8px;">
${i18n.global.t('setting.regionHelper')}
</div>
<ul style="padding-left: 18px; margin: 0;">
<li>${i18n.global.t('setting.regionHelper1')}</li>
<li>${i18n.global.t('setting.regionHelper2')}</li>
<li>${i18n.global.t('setting.regionHelper3')}</li>
</ul>
<div style="margin-bottom: 8px;">
${i18n.global.t('setting.regionHelper4')}
</div>
</div>
`,
i18n.global.t('setting.region'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
dangerouslyUseHTMLString: true,
},
)
.then(async () => {
await onSave('Edition', form.edition);
location.reload();
})
.catch(() => {
form.edition = form.edition === 'cn' ? 'intl' : 'cn';
});
};
onMounted(() => {
search();
getSystemAvailable();