fix: Fix the problem of Docker restarting caused by proxy sync (#8811)

This commit is contained in:
ssongliu 2025-05-25 16:15:17 +08:00 committed by GitHub
parent c323db7e01
commit c65eb7fac8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 288 additions and 21 deletions

View file

@ -72,7 +72,7 @@ func (b *BaseApi) UpdateDaemonJson(c *gin.Context) {
return
}
if err := dockerService.UpdateConf(req); err != nil {
if err := dockerService.UpdateConf(req, true); err != nil {
helper.InternalServer(c, err)
return
}

View file

@ -21,7 +21,7 @@ import (
type DockerService struct{}
type IDockerService interface {
UpdateConf(req dto.SettingUpdate) error
UpdateConf(req dto.SettingUpdate, withRestart bool) error
UpdateLogOption(req dto.LogOption) error
UpdateIpv6Option(req dto.Ipv6Option) error
UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error
@ -136,7 +136,7 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) {
return &data, nil
}
func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) error {
err := createIfNotExistDaemonJsonFile()
if err != nil {
return err
@ -222,9 +222,12 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
}
}
if len(daemonMap) == 0 {
if len(file) == 0 {
return nil
}
_ = os.Remove(constant.DaemonJsonPath)
if err := restartDocker(); err != nil {
return err
if withRestart {
return restartDocker()
}
return nil
}
@ -232,6 +235,9 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if err != nil {
return err
}
if string(newJson) == string(file) {
return nil
}
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
@ -239,8 +245,8 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
return err
}
if err := restartDocker(); err != nil {
return err
if withRestart {
return restartDocker()
}
return nil
}

View file

@ -0,0 +1,83 @@
<template>
<DialogPro v-model="open" :title="$t('commons.msg.infoTitle')" size="small">
<el-form ref="formRef" label-position="top" @submit.prevent>
<el-form-item :label="$t('xpack.node.syncProxyHelper')">
<el-radio-group v-model="restart">
<el-radio :value="true">{{ $t('setting.restartNow') }}</el-radio>
<el-radio :value="false">{{ $t('setting.restartLater') }}</el-radio>
</el-radio-group>
<span class="input-help" v-if="restart">{{ $t('xpack.node.syncProxyHelper1') }}</span>
<span class="input-help" v-else>{{ $t('xpack.node.syncProxyHelper2') }}</span>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="open = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onConfirm">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DialogPro>
</template>
<script lang="ts" setup>
import { getSettingInfo } from '@/api/modules/setting';
const open = ref(false);
const restart = ref(true);
const em = defineEmits(['update:withDockerRestart', 'submit']);
interface DialogProps {
syncList: string;
}
const acceptParams = async (props: DialogProps): Promise<void> => {
if (props.syncList.indexOf('SyncSystemProxy') === -1) {
em('update:withDockerRestart', false);
em('submit');
return;
}
await getSettingInfo()
.then((res) => {
if (res.data.proxyType === '' || res.data.proxyType === 'close') {
em('update:withDockerRestart', false);
em('submit');
return;
}
})
.catch(() => {
em('update:withDockerRestart', false);
em('submit');
return;
});
let searchXSetting;
const xpackModules = import.meta.glob('../../xpack/api/modules/setting.ts', { eager: true });
if (xpackModules['../../xpack/api/modules/setting.ts']) {
searchXSetting = xpackModules['../../xpack/api/modules/setting.ts']['searchXSetting'] || {};
const res = await searchXSetting();
if (!res) {
em('update:withDockerRestart', false);
em('submit');
return;
}
if (res.data.proxyDocker === '') {
em('update:withDockerRestart', false);
em('submit');
return;
}
open.value = true;
}
};
const onConfirm = async () => {
em('update:withDockerRestart', restart.value);
em('submit');
open.value = false;
};
defineExpose({
acceptParams,
});
</script>

View file

@ -0,0 +1,64 @@
<template>
<div v-if="showOption" class="w-full">
<el-radio-group v-model="restart" @change="changeRestart">
<el-radio :value="true">{{ $t('setting.restartNow') }}</el-radio>
<el-radio :value="false">{{ $t('setting.restartLater') }}</el-radio>
</el-radio-group>
<span class="input-help" v-if="restart">{{ $t('xpack.node.syncProxyHelper3') }}</span>
<span class="input-help" v-else>{{ $t('xpack.node.syncProxyHelper4') }}</span>
</div>
</template>
<script lang="ts" setup>
import { getSettingInfo } from '@/api/modules/setting';
const showOption = ref(false);
const restart = ref(false);
const em = defineEmits(['update:withDockerRestart']);
const props = defineProps({
syncList: String,
});
const loadStatus = async () => {
if (props.syncList.indexOf('SyncSystemProxy') === -1) {
em('update:withDockerRestart', false);
return;
}
await getSettingInfo()
.then((res) => {
if (res.data.proxyType === '' || res.data.proxyType === 'close') {
em('update:withDockerRestart', false);
return;
}
})
.catch(() => {
em('update:withDockerRestart', false);
return;
});
let searchXSetting;
const xpackModules = import.meta.glob('../../xpack/api/modules/setting.ts', { eager: true });
if (xpackModules['../../xpack/api/modules/setting.ts']) {
searchXSetting = xpackModules['../../xpack/api/modules/setting.ts']['searchXSetting'] || {};
const res = await searchXSetting();
if (!res) {
em('update:withDockerRestart', false);
return;
}
if (res.data.proxyDocker === '') {
em('update:withDockerRestart', false);
return;
}
showOption.value = true;
}
};
const changeRestart = () => {
em('update:withDockerRestart', restart.value);
};
onMounted(() => {
showOption.value = false;
loadStatus();
});
</script>

View file

@ -23,8 +23,17 @@
{{ $t('license.importHelper') }}
</div>
</el-upload>
<span v-if="!isImport" class="input-help">{{ $t('xpack.node.syncWithMaster') }}</span>
<DockerProxy
v-if="!isImport"
class="w-full mt-2"
v-model:with-docker-restart="withDockerRestart"
syncList="SyncSystemProxy"
/>
</el-col>
</el-row>
<div v-if="oldLicense">
<el-checkbox v-model="isForce">{{ $t('license.updateForce') }}</el-checkbox>
</div>
@ -50,6 +59,7 @@ import i18n from '@/lang';
import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message';
import { uploadLicense } from '@/api/modules/setting';
import DockerProxy from '@/components/docker-proxy/index.vue';
import { GlobalStore } from '@/store';
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
import { getXpackSettingForTheme } from '@/utils/xpack';
@ -62,6 +72,8 @@ const uploaderFiles = ref<UploadFiles>([]);
const isImport = ref();
const isForce = ref();
const withDockerRestart = ref();
const oldLicense = ref();
interface DialogProps {
oldLicense: string;
@ -112,6 +124,7 @@ const submit = async () => {
}
if (!isImport.value) {
formData.append('currentNode', globalStore.currentNode);
formData.append('withDockerRestart', withDockerRestart.value);
}
formData.append('isForce', isForce.value);
loading.value = true;

View file

@ -1598,6 +1598,7 @@ const message = {
confDockerProxy: 'Configure Docker Proxy',
restartNowHelper: 'Configuring Docker proxy requires restarting the Docker service.',
restartNow: 'Restart immediately',
restartLater: 'Restart manually later',
systemIPWarning:
'The current node has no default access address configured. Please go to panel settings to configure it!',
systemIPWarning1: 'The current server address is set to {0}, and quick redirection is not possible!',
@ -3331,7 +3332,16 @@ const message = {
syncInfo: 'Sync',
syncHelper: 'When master node data changes, it synchronizes to this child node in real-time',
syncBackupAccount: 'Backup account settings',
syncWithMaster:
'After upgrading to Pro, all data will be synced by default. Sync policies can be manually adjusted in node management.',
syncProxy: 'System proxy settings',
syncProxyHelper: 'Syncing system proxy settings requires Docker restart',
syncProxyHelper1: 'Restarting Docker may affect currently running container services.',
syncProxyHelper2: 'You can manually restart in the Containers - Configuration page.',
syncProxyHelper3:
'Syncing system proxy settings requires Docker restart, which may affect currently running container services',
syncProxyHelper4:
'Syncing system proxy settings requires Docker restart. You can manually restart later in the Containers - Configuration page.',
syncCustomApp: 'Sync Custom App Repository',
syncAlertSetting: 'System alert settings',
syncNodeInfo: 'Node basic data,',

View file

@ -1532,6 +1532,7 @@ const message = {
confDockerProxy: 'Dockerプロキシを構成します',
restartNowHelper: 'Dockerプロキシの構成にはDockerサービスを再起動する必要があります',
restartNow: 'すぐに再起動します',
restartLater: '後で手動で再起動',
systemIPWarning:
'現在のノードにはデフォルトアクセスアドレスが設定されていませんパネル設定から設定してください',
systemIPWarning1: `現在のサーバーアドレスは{0}に設定されており、クイックリダイレクトは不可能です!`,
@ -3188,7 +3189,16 @@ const message = {
syncInfo: 'データ同期,',
syncHelper: 'マスターノードのデータが変更されるとこの子ノードにリアルタイムで同期されます,',
syncBackupAccount: 'バックアップアカウント設定',
syncWithMaster:
'プロ版にアップグレード後すべてのデータがデフォルトで同期されますノード管理で同期ポリシーを手動調整できます',
syncProxy: 'システムプロキシ設定',
syncProxyHelper: 'システムプロキシ設定の同期にはDockerの再起動が必要です',
syncProxyHelper1: 'Dockerの再起動は現在実行中のコンテナサービスに影響する可能性があります',
syncProxyHelper2: 'コンテナ - 設定 ページで手動で再起動できます',
syncProxyHelper3:
'システムプロキシ設定の同期にはDockerの再起動が必要で現在実行中のコンテナサービスに影響する可能性があります',
syncProxyHelper4:
'システムプロキシ設定の同期にはDockerの再起動が必要です後でコンテナ - 設定 ページで手動で再起動できます',
syncCustomApp: 'カスタムアプリリポジトリを同期',
syncAlertSetting: 'システムアラート設定',
syncNodeInfo: 'ノード基本データ,',

View file

@ -1518,6 +1518,7 @@ const message = {
confDockerProxy: 'Docker 프록시 구성',
restartNowHelper: 'Docker 프록시 구성을 위해 Docker 서비스를 재시작해야 합니다.',
restartNow: '즉시 재시작',
restartLater: '나중에 수동으로 재시작',
systemIPWarning: '현재 노드에 기본 접근 주소가 설정되지 않았습니다. 패널 설정에서 설정해 주세요!',
systemIPWarning1: '현재 서버 주소는 {0}으로 설정되어 있어 빠른 리디렉션이 불가능합니다!',
defaultNetwork: '네트워크 카드',
@ -3133,7 +3134,16 @@ const message = {
syncInfo: '데이터 동기화,',
syncHelper: '마스터 노드 데이터가 변경되면, 자식 노드에 실시간으로 동기화됩니다,',
syncBackupAccount: '백업 계정 설정',
syncWithMaster:
'프로 버전으로 업그레이드 모든 데이터가 기본적으로 동기화됩니다. 노드 관리에서 동기화 정책을 수동으로 조정할 있습니다.',
syncProxy: '시스템 프록시 설정',
syncProxyHelper: '시스템 프록시 설정 동기화에는 Docker 재시작이 필요합니다',
syncProxyHelper1: 'Docker 재시작은 현재 실행 중인 컨테이너 서비스에 영향을 있습니다.',
syncProxyHelper2: '컨테이너 - 설정 페이지에서 수동으로 재시작할 있습니다.',
syncProxyHelper3:
'시스템 프록시 설정 동기화에는 Docker 재시작이 필요하며, 현재 실행 중인 컨테이너 서비스에 영향을 있습니다',
syncProxyHelper4:
'시스템 프록시 설정 동기화에는 Docker 재시작이 필요합니다. 나중에 컨테이너 - 설정 페이지에서 수동으로 재시작할 있습니다.',
syncCustomApp: '사용자 정의 저장소 동기화',
syncAlertSetting: '시스템 경고 설정',
syncNodeInfo: '노드 기본 데이터,',

View file

@ -1582,6 +1582,7 @@ const message = {
confDockerProxy: 'Konfigurasi proksi docker',
restartNowHelper: 'Mengkonfigurasi proksi Docker memerlukan memulakan semula perkhidmatan Docker.',
restartNow: 'Mulakan semula sekarang',
restartLater: 'Mulakan semula secara manual nanti',
systemIPWarning:
'Nod semasa belum mempunyai alamat akses lalai yang dikonfigurasi. Sila pergi ke tetapan panel untuk mengkonfigurasinya!',
systemIPWarning1: 'Alamat pelayan semasa ditetapkan kepada {0}, dan pengalihan cepat tidak mungkin!',
@ -3258,7 +3259,16 @@ const message = {
syncInfo: 'Penyegerakan data,',
syncHelper: 'Apabila data nod induk berubah, ia akan disegerakkan ke nod anak ini secara masa nyata,',
syncBackupAccount: 'Tetapan akaun sandaran',
syncWithMaster:
'Selepas menaik taraf ke Pro, semua data akan diselaraskan secara lalai. Dasar penyelarasan boleh disesuaikan secara manual dalam pengurusan nod.',
syncProxy: 'Tetapan proksi sistem',
syncProxyHelper: 'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker',
syncProxyHelper1: 'Memulakan semula Docker mungkin menjejaskan perkhidmatan kontena yang sedang berjalan.',
syncProxyHelper2: 'Anda boleh mulakan semula secara manual di halaman Kontena - Konfigurasi.',
syncProxyHelper3:
'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker, yang mungkin menjejaskan perkhidmatan kontena yang sedang berjalan',
syncProxyHelper4:
'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker. Anda boleh mulakan semula secara manual di halaman Kontena - Konfigurasi nanti.',
syncCustomApp: 'Segerakan Repositori Aplikasi Tersuai',
syncAlertSetting: 'Tetapan amaran sistem',
syncNodeInfo: 'Data asas nod,',

View file

@ -1565,6 +1565,7 @@ const message = {
confDockerProxy: 'Configurar proxy do Docker',
restartNowHelper: 'Configurar o proxy do Docker exige reiniciar o serviço Docker.',
restartNow: 'Reiniciar imediatamente',
restartLater: 'Reiniciar manualmente mais tarde',
systemIPWarning:
'O atual não tem um endereço de acesso padrão configurado. Por favor, para as configurações do painel para configurá-lo!',
systemIPWarning1:
@ -3265,7 +3266,16 @@ const message = {
syncInfo: 'Sincronização de dados,',
syncHelper: 'Quando os dados do mestre mudam, são sincronizados em tempo real para este filho,',
syncBackupAccount: 'Configurações de conta de backup',
syncWithMaster:
'Após atualizar para Pro, todos os dados serão sincronizados por padrão. As políticas de sincronização podem ser ajustadas manualmente no gerenciamento de nós.',
syncProxy: 'Configurações de proxy do sistema',
syncProxyHelper: 'Sincronizar configurações de proxy do sistema requer reinicialização do Docker',
syncProxyHelper1: 'Reiniciar o Docker pode afetar os serviços de contêiner em execução.',
syncProxyHelper2: 'Você pode reiniciar manualmente na página Contêineres - Configuração.',
syncProxyHelper3:
'Sincronizar configurações de proxy do sistema requer reinicialização do Docker, o que pode afetar os serviços de contêiner em execução',
syncProxyHelper4:
'Sincronizar configurações de proxy do sistema requer reinicialização do Docker. Você pode reiniciar manualmente mais tarde na página Contêineres - Configuração.',
syncCustomApp: 'Sincronizar Repositório de Aplicativos Personalizados',
syncAlertSetting: 'Configurações de alerta do sistema',
syncNodeInfo: 'Dados básicos do ,',

View file

@ -1568,6 +1568,7 @@ const message = {
confDockerProxy: 'Настроить прокси docker',
restartNowHelper: 'Настройка прокси Docker требует перезапуска службы Docker.',
restartNow: 'Перезапустить немедленно',
restartLater: 'Перезагрузить вручную позже',
systemIPWarning:
'Текущий узел не имеет настроенного адреса доступа по умолчанию. Пожалуйста, перейдите в настройки панели для его настройки!',
systemIPWarning1: 'Текущий адрес сервера установлен на {0}, быстрое перенаправление невозможно!',
@ -3252,7 +3253,16 @@ const message = {
syncHelper:
'При изменении данных главного узла, происходит синхронизация с этим дочерним узлом в реальном времени,',
syncBackupAccount: 'Настройки резервной учётной записи',
syncWithMaster:
'После обновления до Pro все данные будут синхронизироваться по умолчанию. Политики синхронизации можно настроить вручную в управлении узлами.',
syncProxy: 'Настройки системного прокси',
syncProxyHelper: 'Синхронизация настроек системного прокси требует перезапуска Docker',
syncProxyHelper1: 'Перезапуск Docker может повлиять на работающие сервисы контейнеров.',
syncProxyHelper2: 'Вы можете перезапустить вручную на странице Контейнеры - Конфигурация.',
syncProxyHelper3:
'Синхронизация настроек системного прокси требует перезапуска Docker, что может повлиять на работающие сервисы контейнеров',
syncProxyHelper4:
'Синхронизация настроек системного прокси требует перезапуска Docker. Вы можете перезапустить вручную позже на странице Контейнеры - Конфигурация.',
syncCustomApp: 'Синхронизировать пользовательский репозиторий приложений',
syncAlertSetting: 'Настройки системных предупреждений',
syncNodeInfo: 'Базовые данные узла,',

View file

@ -1510,6 +1510,7 @@ const message = {
confDockerProxy: '配寘 Docker 代理',
restartNowHelper: '配寘 Docker 代理需要重啓 Docker 服務',
restartNow: '立即重啓',
restartLater: '稍後手動重啟',
systemIPWarning: '當前節點尚未配置預設存取地址請前往面板設定進行設定',
systemIPWarning1: '當前服務器地址設置為 {0}無法快速跳轉',
changePassword: '密碼修改',
@ -3087,7 +3088,13 @@ const message = {
syncInfo: '數據同步,',
syncHelper: '當主節點數據發生變化時實時同步到該子節點,',
syncBackupAccount: '備份帳號設定',
syncWithMaster: '升級為專業版後將預設同步所有資料可在節點管理中手動調整同步策略',
syncProxy: '系統代理設定',
syncProxyHelper: '同步系統代理設定需要重啟 Docker',
syncProxyHelper1: '重啟 Docker 可能會影響當前正在運行的容器服務',
syncProxyHelper2: '可前往 容器 - 設定 頁面手動重啟',
syncProxyHelper3: '同步系統代理設定需要重啟 Docker重啟可能會影響當前正在運行的容器服務',
syncProxyHelper4: '同步系統代理設定需要重啟 Docker可稍後前往 容器 - 設定 頁面手動重啟',
syncCustomApp: '同步自訂應用倉庫',
syncAlertSetting: '系統告警設定',
syncNodeInfo: '節點基礎數據,',

View file

@ -1506,6 +1506,7 @@ const message = {
confDockerProxy: '配置 Docker 代理',
restartNowHelper: '配置 Docker 代理需要重启 Docker 服务',
restartNow: '立即重启',
restartLater: '稍后手动重启',
systemIPWarning: '当前节点尚未配置默认访问地址请前往面板设置进行设置',
systemIPWarning1: '当前服务器地址设置为 {0}无法快速跳转',
changePassword: '密码修改',
@ -3069,7 +3070,13 @@ const message = {
syncInfo: '数据同步',
syncHelper: '主节点数据发生变化时实时同步到该子节点',
syncBackupAccount: '备份账号设置',
syncWithMaster: '升级为专业版后将默认同步所有数据可在节点管理中手动调整同步策略',
syncProxy: '系统代理设置',
syncProxyHelper: '同步系统代理设置需要重启 Docker',
syncProxyHelper1: '重启 Docker 可能会影响当前正在运行的容器服务',
syncProxyHelper2: '可前往 容器 - 配置 页面手动重启',
syncProxyHelper3: '同步系统代理设置需要重启 Docker重启可能会影响当前正在运行的容器服务',
syncProxyHelper4: '同步系统代理设置需要重启 Docker可稍后前往 容器 - 配置 页面手动重启',
syncCustomApp: '同步自定义应用仓库',
syncAlertSetting: '系统告警设置',
syncNodeInfo: '节点基础数据',

View file

@ -18,10 +18,18 @@
</div>
<el-form-item prop="syncListItem">
<el-checkbox-group v-model="form.syncListItem">
<div class="ml-5">
<el-checkbox :label="$t('xpack.node.syncProxy')" value="SyncSystemProxy" />
</div>
<div class="ml-5">
<el-checkbox :label="$t('xpack.node.syncAlertSetting')" value="SyncAlertSetting" />
</div>
<div class="ml-5">
<el-checkbox :label="$t('xpack.node.syncCustomApp')" value="SyncCustomApp" />
</div>
<div class="ml-5">
<el-checkbox :label="$t('xpack.node.syncBackupAccount')" value="SyncBackupAccounts" />
</div>
</el-checkbox-group>
<span class="input-help">{{ $t('xpack.node.syncHelper') }}</span>
</el-form-item>
@ -34,12 +42,15 @@
</el-button>
</template>
</DrawerPro>
<DockerProxyDialog ref="dockerProxyRef" @submit="submit" v-model:with-docker-restart="withDockerRestart" />
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { bindLicense, listNodeOptions } from '@/api/modules/setting';
import DockerProxyDialog from '@/components/docker-proxy/dialog.vue';
import { FormInstance } from 'element-plus';
import { GlobalStore } from '@/store';
import { Rules } from '@/global/form-rules';
@ -54,6 +65,9 @@ const loading = ref();
const licenseName = ref();
const freeNodes = ref([]);
const dockerProxyRef = ref();
const withDockerRestart = ref(true);
const form = reactive({
nodeID: null,
licenseID: null,
@ -74,8 +88,13 @@ const onBind = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
form.syncList = form.syncListItem?.join(',') || '';
dockerProxyRef.value.acceptParams({ syncList: form.syncList });
});
};
const submit = async () => {
loading.value = true;
await bindLicense(form.licenseID, form.nodeID, form.syncList)
.then(() => {
loading.value = false;
@ -85,7 +104,6 @@ const onBind = async (formEl: FormInstance | undefined) => {
.catch(() => {
loading.value = false;
});
});
};
const loadNodes = async () => {

View file

@ -86,6 +86,12 @@
{{ $t('license.forceUnbindHelper') }}
</span>
</el-form-item>
<DockerProxy
class="w-full"
v-model:with-docker-restart="withDockerRestart"
syncList="SyncSystemProxy"
/>
</el-form>
</template>
</OpDialog>
@ -96,6 +102,7 @@
import { ref, reactive, onMounted } from 'vue';
import { deleteLicense, searchLicense, syncLicense, unbindLicense } from '@/api/modules/setting';
import LicenseImport from '@/components/license-import/index.vue';
import DockerProxy from '@/components/docker-proxy/index.vue';
import BindFree from '@/views/setting/license/bind/free.vue';
import BindXpack from '@/views/setting/license/bind/xpack.vue';
import { dateFormat } from '@/utils/util';
@ -111,6 +118,8 @@ const opRef2 = ref();
const forceUnbind = ref();
const unbindRow = ref();
const withDockerRestart = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'license-page-size',