fix: Fix some of the problems of node switching (#8573)

This commit is contained in:
ssongliu 2025-05-08 18:01:42 +08:00 committed by GitHub
parent 099ca7ff6c
commit 69d24a8b90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 118 additions and 71 deletions

View file

@ -32,7 +32,7 @@ func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
// @Security Timestamp
// @Router /dashboard/app/launcher [get]
func (b *BaseApi) LoadAppLauncher(c *gin.Context) {
data, err := dashboardService.LoadAppLauncher()
data, err := dashboardService.LoadAppLauncher(c)
if err != nil {
helper.InternalServer(c, err)
return

View file

@ -140,9 +140,8 @@ type AppLauncher struct {
Name string `json:"name"`
Icon string `json:"icon"`
Limit int `json:"limit"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
Recommend int `json:"recomend"`
Description string `json:"description"`
Recommend int `json:"recommend"`
IsInstall bool `json:"isInstall"`
IsRecommend bool `json:"isRecommend"`

View file

@ -21,6 +21,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
@ -37,7 +38,7 @@ type IDashboardService interface {
LoadCurrentInfoForNode() *dto.NodeCurrent
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
LoadAppLauncher() ([]dto.AppLauncher, error)
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
ChangeShow(req dto.SettingUpdate) error
ListLauncherOption(filter string) ([]dto.LauncherOption, error)
Restart(operation string) error
@ -263,7 +264,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
return &currentInfo
}
func (u *DashboardService) LoadAppLauncher() ([]dto.AppLauncher, error) {
func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) {
var (
data []dto.AppLauncher
recommendList []dto.AppLauncher
@ -289,9 +290,8 @@ func (u *DashboardService) LoadAppLauncher() ([]dto.AppLauncher, error) {
itemData.Name = app.Name
itemData.Icon = app.Icon
itemData.Limit = app.Limit
itemData.ShortDescEn = app.ShortDescEn
itemData.ShortDescZh = app.ShortDescZh
itemData.Recommend = app.Recommend
itemData.Description = app.GetDescription(ctx)
break
}
}

View file

@ -283,6 +283,11 @@ func snapBaseData(snap snapHelper, targetDir string) error {
if err != nil {
return err
}
err = snap.FileOp.CopyFile("/usr/local/bin/1pctl", targetDir)
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
}
err := snap.FileOp.CopyFile("/usr/local/bin/1panel-agent", targetDir)
@ -291,12 +296,6 @@ func snapBaseData(snap snapHelper, targetDir string) error {
return err
}
err = snap.FileOp.CopyFile("/usr/local/bin/1pctl", targetDir)
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
if global.IsMaster {
err = snap.FileOp.CopyFile("/etc/systemd/system/1panel-core.service", targetDir)
snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/etc/systemd/system/1panel-core.service"), err)

View file

@ -246,12 +246,12 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
}
}
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1pctl", baseDir)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
if global.IsMaster {
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1pctl", baseDir)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
err = itemHelper.FileOp.CopyFile("/usr/local/bin/1panel-core", baseDir)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err)
if err != nil {
@ -328,13 +328,12 @@ func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
itemHelper.Task.Log("---------------------- 6 / 10 ----------------------")
itemHelper.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo"))
err := itemHelper.FileOp.CopyFile(path.Join(src, "1pctl"), "/usr/local/bin")
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
if global.IsMaster {
err := itemHelper.FileOp.CopyFile(path.Join(src, "1pctl"), "/usr/local/bin")
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err)
if err != nil {
return err
}
err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-core"), "/usr/local/bin")
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err)
if err != nil {
@ -346,7 +345,7 @@ func recoverBaseData(src string, itemHelper *snapRecoverHelper) error {
return err
}
}
err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-agent"), "/usr/local/bin")
err := itemHelper.FileOp.CopyFile(path.Join(src, "1panel-agent"), "/usr/local/bin")
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err)
if err != nil {
return err

View file

@ -25,6 +25,9 @@
</el-upload>
</el-col>
</el-row>
<div v-if="oldLicense">
<el-checkbox v-model="isForce">{{ $t('license.updateForce') }}</el-checkbox>
</div>
<el-button
type="primary"
class="mt-3 w-52 custom-button"
@ -57,6 +60,7 @@ const open = ref(false);
const uploadRef = ref<UploadInstance>();
const uploaderFiles = ref<UploadFiles>([]);
const isImport = ref();
const isForce = ref();
const oldLicense = ref();
interface DialogProps {
@ -109,6 +113,7 @@ const submit = async () => {
if (!isImport.value) {
formData.append('currentNode', globalStore.currentNode);
}
formData.append('isForce', isForce.value);
loading.value = true;
await uploadLicense(oldLicense.value, formData)
.then(async () => {

View file

@ -21,6 +21,8 @@ import i18n from '@/lang';
import { MsgError, MsgWarning } from '@/utils/message';
import { jumpToPath } from '@/utils/util';
import { useRouter } from 'vue-router';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const router = useRouter();
const open = ref();
@ -39,8 +41,12 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
let protocol = params.protocol === 'https' ? 'https' : 'http';
const res = await getAgentSettingInfo();
if (!res.data.systemIP) {
open.value = true;
return;
if (!globalStore.currentNodeAddr) {
open.value = true;
return;
} else {
res.data.systemIP = globalStore.currentNodeAddr;
}
}
if (res.data.systemIP.indexOf(':') === -1) {
if (params.ip && params.ip === 'ipv6') {

View file

@ -602,7 +602,9 @@ const message = {
containerConnHelper:
'This connection address is used by applications running on the PHP execution environment/container installation.',
remoteConn: 'External Connection',
remoteConnHelper2: 'Use this address for non-container or external connections',
remoteConnHelper2: 'Use this address for non-container environments or external connections.',
remoteConnHelper3:
'The default access address is the host IP. To modify it, go to the "Default Access Address" configuration item in the panel settings page.',
localIP: 'Local IP',
},
aiTools: {
@ -1922,6 +1924,7 @@ const message = {
forceUnbind: 'Force Unbind',
forceUnbindHelper:
'Forcing unbind will ignore any errors that occur during the unbinding process and ultimately release the license binding.',
updateForce: 'Force update (ignore all errors during unbinding to ensure final operation succeeds)',
trialInfo: 'Version',
authorizationId: 'Authorization ID',
authorizedUser: 'Authorized User',

View file

@ -590,8 +590,9 @@ const message = {
containerConnHelper:
'この接続アドレスはWebサイトのランタイムPHPなどまたはコンテナで実行されているアプリケーションで使用できます',
remoteConn: '外部接続',
remoteConnHelper2:
'この接続アドレスは非コンテナまたは外部アプリケーションで実行されているアプリケーションで使用できます',
remoteConnHelper2: 'コンテナ環境以外または外部接続にはこのアドレスを使用してください',
remoteConnHelper3:
'デフォルトアクセスアドレスはホストIPです変更するにはパネル設定ページのデフォルトアクセスアドレス設定項目へ移動してください',
localIP: 'ローカルIP',
},
aiTools: {
@ -1831,6 +1832,7 @@ const message = {
forceUnbind: '強制バインド解除',
forceUnbindHelper:
'強制的にバインド解除を行うと解除プロセス中に発生するエラーを無視し最終的にライセンスのバインドを解除します',
updateForce: '強制更新アンバインド中のすべてのエラーを無視し最終操作の成功を保証します',
trialInfo: 'バージョン',
authorizationId: 'サブスクリプション承認ID',
authorizedUser: '認定ユーザー',

View file

@ -587,8 +587,9 @@ const message = {
containerConnHelper:
' 연결 주소는 웹사이트 런타임(PHP ) 또는 컨테이너에서 실행 중인 애플리케이션에서 사용할 있습니다.',
remoteConn: '외부 연결',
remoteConnHelper2:
' 연결 주소는 컨테이너 외부 또는 외부 애플리케이션에서 실행 중인 애플리케이션에서 사용할 있습니다.',
remoteConnHelper2: '컨테이너 환경이 아닌 경우 또는 외부 연결에는 주소를 사용하십시오.',
remoteConnHelper3:
'기본 접근 주소는 호스트 IP입니다. 수정하려면 패널 설정 페이지의 "기본 접근 주소" 구성 항목으로 이동하세요.',
localIP: '로컬 IP',
},
aiTools: {
@ -1801,6 +1802,7 @@ const message = {
forceUnbind: '강제 바인딩 해제',
forceUnbindHelper:
'강제 바인딩 해제를 수행하면 해제 과정에서 발생하는 오류를 무시하고 궁극적으로 라이센스 바인딩을 해제합니다.',
updateForce: '강제 업데이트 (바인딩 해제 과정의 모든 오류를 무시하고 최종 작업 성공을 보장합니다)',
trialInfo: '버전',
authorizationId: '구독 인증 ID',
authorizedUser: '인증된 사용자',

View file

@ -602,8 +602,9 @@ const message = {
containerConnHelper:
'Alamat sambungan ini boleh digunakan oleh aplikasi yang berjalan pada runtime laman web (PHP, dll.) atau kontena.',
remoteConn: 'Sambungan luaran',
remoteConnHelper2:
'Alamat sambungan ini boleh digunakan oleh aplikasi yang berjalan di luar kontena atau aplikasi luaran.',
remoteConnHelper2: 'Gunakan alamat ini untuk persekitaran bukan kontena atau sambungan luar.',
remoteConnHelper3:
'Alamat akses lalai ialah IP hos. Untuk mengubahnya, pergi ke item konfigurasi "Alamat Akses Lalai" pada halaman tetapan panel.',
localIP: 'IP Tempatan',
},
aiTools: {
@ -1889,6 +1890,7 @@ const message = {
forceUnbind: 'Paksakan Nyahikat',
forceUnbindHelper:
'Memaksa nyahikat akan mengabaikan sebarang ralat yang berlaku semasa proses nyahikat dan akhirnya melepaskan ikatan lesen.',
updateForce: 'Kemas kini paksa (abaikan semua ralat semasa nyahikatan untuk memastikan operasi akhir berjaya)',
trialInfo: 'Versi',
authorizationId: 'ID Kebenaran Langganan',
authorizedUser: 'Pengguna yang Dibenarkan',

View file

@ -599,8 +599,9 @@ const message = {
containerConnHelper:
'Este endereço de conexão pode ser utilizado por aplicações que estão em execução nos ambientes do site (PHP, etc.) ou no contêiner.',
remoteConn: 'Conexão externa',
remoteConnHelper2:
'Este endereço de conexão pode ser utilizado por aplicações que estão fora do contêiner ou por aplicações externas.',
remoteConnHelper2: 'Use este endereço para ambientes não-container ou conexões externas.',
remoteConnHelper3:
'O endereço de acesso padrão é o IP do host. Para modificá-lo, acesse o item de configuração "Endereço de Acesso Padrão" na página de configurações do painel.',
localIP: 'IP local',
},
aiTools: {
@ -1876,6 +1877,8 @@ const message = {
forceUnbind: 'Forçar Desvinculação',
forceUnbindHelper:
'Forçar a desvinculação ignorará quaisquer erros que ocorram durante o processo de desvinculação e, em última análise, liberará a vinculação da licença.',
updateForce:
'Atualização forçada (ignora todos os erros durante o desvinculamento para garantir o sucesso da operação final)',
trialInfo: 'Versão',
authorizationId: 'ID de autorização',
authorizedUser: 'Usuário autorizado',

View file

@ -597,8 +597,9 @@ const message = {
containerConnHelper:
'Этот адрес подключения может использоваться приложениями, работающими в среде выполнения веб-сайта (PHP и т.д.) или контейнере.',
remoteConn: 'Внешнее подключение',
remoteConnHelper2:
'Этот адрес подключения может использоваться приложениями, работающими вне контейнера или внешними приложениями.',
remoteConnHelper2: 'Используйте этот адрес для неконтейнерных сред или внешних подключений.',
remoteConnHelper3:
'Адрес доступа по умолчанию - это IP хоста. Для изменения перейдите к пункту конфигурации "Адрес доступа по умолчанию" на странице настроек панели.',
localIP: 'Локальный IP',
},
aiTools: {
@ -1875,6 +1876,8 @@ const message = {
forceUnbind: 'Принудительное отвязывание',
forceUnbindHelper:
'Принудительное отвязывание будет игнорировать любые ошибки, возникающие в процессе отвязывания, и в конечном итоге освободит привязку лицензии.',
updateForce:
'Принудительное обновление (игнорировать все ошибки при отвязке для гарантии успешного завершения операции)',
trialInfo: 'Версия',
authorizationId: 'ID авторизации подписки',
authorizedUser: 'Авторизованный пользователь',

View file

@ -583,7 +583,8 @@ const message = {
connAddress: '地址',
containerConnHelper: 'PHP 執行環境/容器安裝的應用程式使用此連接地址',
remoteConn: '外部連接',
remoteConnHelper2: '非容器或外部連接使用此地址',
remoteConnHelper2: '非容器環境或外部連接需使用此地址',
remoteConnHelper3: '預設存取地址為主機IP修改請前往面板設定頁面的預設存取地址配置項',
localIP: '本機 IP',
},
aiTools: {
@ -1785,6 +1786,7 @@ const message = {
versionConstraint: '{0} 版本買斷',
forceUnbind: '強制解除綁定',
forceUnbindHelper: '強制解除綁定將忽略解除過程中產生的錯誤最終解除許可證綁定',
updateForce: '強制更新忽略解除綁定過程中的所有錯誤確保最終操作成功',
trialInfo: '版本',
authorizationId: '訂閱授權 ID',
authorizedUser: '被授權方',

View file

@ -581,7 +581,8 @@ const message = {
connAddress: '地址',
containerConnHelper: 'PHP 运行环境/容器安装的应用使用此连接地址',
remoteConn: '外部连接',
remoteConnHelper2: '非容器或外部连接使用此地址',
remoteConnHelper2: '非容器环境或外部连接需使用此地址',
remoteConnHelper3: '默认访问地址为主机IP修改请前往面板设置页面的默认访问地址配置项',
localIP: '本机 IP',
},
aiTools: {
@ -1778,6 +1779,7 @@ const message = {
versionConstraint: '{0} 版本买断',
forceUnbind: '强制解绑',
forceUnbindHelper: '强制解绑会忽略解绑过程中产生的错误并最终解除许可证绑定',
updateForce: '强制更新忽略解绑过程中的所有错误确保最终操作成功',
trialInfo: '版本',
authorizationId: '订阅授权 ID',
authorizedUser: '被授权方',

View file

@ -165,6 +165,7 @@ const changeNode = (command: string) => {
}
if (command == 'local') {
globalStore.currentNode = command || 'local';
globalStore.currentNodeAddr = '';
globalStore.isOffline = false;
router.push({ name: 'home' }).then(() => {
window.location.reload();
@ -186,6 +187,7 @@ const changeNode = (command: string) => {
return;
}
globalStore.currentNode = command || 'local';
globalStore.currentNodeAddr = item.addr;
globalStore.isOffline = item.isOffline;
router.push({ name: 'home' }).then(() => {
window.location.reload();

View file

@ -44,6 +44,7 @@ export interface GlobalState {
errStatus: string;
currentNode: string;
currentNodeAddr: string;
isOffline: boolean;
}

View file

@ -47,6 +47,7 @@ const GlobalStore = defineStore({
errStatus: '',
currentNode: 'local',
currentNodeAddr: '',
isOffline: false,
}),
getters: {

View file

@ -3,7 +3,7 @@
<docker-status v-model:isActive="isActive" v-model:isExist="isExist" @search="search" />
<LayoutContent :title="$t('menu.container')" v-if="isExist" :class="{ mask: !isActive }">
<template #search>
<template #search v-if="tags.length !== 0">
<div class="card-interval" v-if="isExist && isActive">
<div v-for="item in tags" :key="item.key" class="inline">
<el-button
@ -492,14 +492,30 @@ const searchWithAppShow = (item: any) => {
const loadContainerCount = async () => {
await loadContainerStatus().then((res) => {
tags.value = [];
tags.value.push({ key: 'all', count: res.data.all });
tags.value.push({ key: 'running', count: res.data.running });
tags.value.push({ key: 'paused', count: res.data.paused });
tags.value.push({ key: 'restarting', count: res.data.restarting });
tags.value.push({ key: 'removing', count: res.data.removing });
tags.value.push({ key: 'created', count: res.data.created });
tags.value.push({ key: 'dead', count: res.data.dead });
tags.value.push({ key: 'exited', count: res.data.exited });
if (res.data.all) {
tags.value.push({ key: 'all', count: res.data.all });
}
if (res.data.all) {
tags.value.push({ key: 'running', count: res.data.running });
}
if (res.data.all) {
tags.value.push({ key: 'paused', count: res.data.paused });
}
if (res.data.all) {
tags.value.push({ key: 'restarting', count: res.data.restarting });
}
if (res.data.all) {
tags.value.push({ key: 'removing', count: res.data.removing });
}
if (res.data.all) {
tags.value.push({ key: 'created', count: res.data.created });
}
if (res.data.all) {
tags.value.push({ key: 'dead', count: res.data.dead });
}
if (res.data.all) {
tags.value.push({ key: 'exited', count: res.data.exited });
}
});
};

View file

@ -181,12 +181,8 @@ const loadAccess = async () => {
};
const loadSystemIP = async () => {
if (globalStore.currentNode !== 'local') {
form.systemIP = globalStore.currentNode || i18n.global.t('database.localIP');
return;
}
const res = await getAgentSettingInfo();
form.systemIP = res.data.systemIP || i18n.global.t('database.localIP');
form.systemIP = res.data.systemIP || globalStore.currentNodeAddr || i18n.global.t('database.localIP');
};
const loadPassword = async () => {

View file

@ -181,12 +181,8 @@ const loadAccess = async () => {
};
const loadSystemIP = async () => {
if (globalStore.currentNode !== 'local') {
form.systemIP = globalStore.currentNode || i18n.global.t('database.localIP');
return;
}
const res = await getAgentSettingInfo();
form.systemIP = res.data.systemIP || i18n.global.t('database.localIP');
form.systemIP = res.data.systemIP || globalStore.currentNodeAddr || i18n.global.t('database.localIP');
};
const loadPassword = async () => {

View file

@ -183,12 +183,8 @@ const loadPassword = async () => {
};
const loadSystemIP = async () => {
if (globalStore.currentNode !== 'local') {
form.systemIP = globalStore.currentNode || i18n.global.t('database.localIP');
return;
}
const res = await getAgentSettingInfo();
form.systemIP = res.data.systemIP || i18n.global.t('database.localIP');
form.systemIP = res.data.systemIP || globalStore.currentNodeAddr || i18n.global.t('database.localIP');
};
function loadRedisInfo(isContainer: boolean) {

View file

@ -37,9 +37,7 @@
</div>
<div class="h-app-desc">
<span>
{{
language == 'zh' || language == 'tw' ? app.shortDescZh : app.shortDescEn
}}
{{ app.description }}
</span>
</div>
</div>
@ -183,12 +181,10 @@ import { MsgSuccess } from '@/utils/message';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { toFolder } from '@/global/business';
import { getLanguage } from '@/utils/util';
const router = useRouter();
const globalStore = GlobalStore();
const language = getLanguage();
let loading = ref(false);
let apps = ref([]);
const options = ref([]);

View file

@ -12,6 +12,19 @@
<el-option v-for="item in freeNodes" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-card class="mt-5" v-if="form.nodeID && form.nodeID !== 0">
<div class="mb-2">
<span>{{ $t('xpack.node.syncInfo') }}</span>
</div>
<el-form-item prop="syncListItem">
<el-checkbox-group v-model="form.syncListItem">
<el-checkbox :label="$t('xpack.node.syncProxy')" value="SyncSystemProxy" />
<el-checkbox :label="$t('xpack.node.syncAlertSetting')" value="SyncAlertSetting" />
<el-checkbox :label="$t('xpack.node.syncCustomApp')" value="SyncCustomApp" />
</el-checkbox-group>
<span class="input-help">{{ $t('xpack.node.syncHelper') }}</span>
</el-form-item>
</el-card>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
@ -43,6 +56,8 @@ const freeNodes = ref([]);
const form = reactive({
nodeID: null,
licenseID: null,
syncList: '',
syncListItem: ['SyncSystemProxy', 'SyncAlertSetting', 'SyncCustomApp'],
});
const formRef = ref<FormInstance>();
@ -59,6 +74,7 @@ const onBind = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
form.syncList = form.syncListItem?.join(',') || '';
await bindLicense(form.licenseID, form.nodeID)
.then(() => {
loading.value = false;

View file

@ -255,7 +255,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: (row: any) => {
licenseRef.value.acceptParams({ oldLicense: row.licenseName });
licenseRef.value.acceptParams({ oldLicense: row.licenseName, isImport: true });
},
},
{