mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-27 09:49:28 +08:00
fix: Fix the display exception problem when the database application does not exist (#8094)
This commit is contained in:
parent
f80dffd908
commit
08e2a70206
18 changed files with 95 additions and 123 deletions
|
|
@ -107,6 +107,8 @@ type SearchForSize struct {
|
|||
DetailName string `json:"detailName"`
|
||||
Info string `json:"info"`
|
||||
CronjobID uint `json:"cronjobID"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
type RecordFileSize struct {
|
||||
ID uint `json:"id"`
|
||||
|
|
|
|||
|
|
@ -202,6 +202,13 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||
|
||||
recoverApp := func(t *task.Task) error {
|
||||
fileOp := files.NewFileOp()
|
||||
if !isRollback {
|
||||
rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleAppBackup(install, nil, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||
t.Log(fmt.Sprintf("backup app %s for rollback before recover failed, err: %v", install.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -226,13 +233,6 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||
return errors.New(i18n.GetMsgByKey("AppAttributesNotMatch"))
|
||||
}
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleAppBackup(install, nil, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||
t.Log(fmt.Sprintf("backup app %s for rollback before recover failed, err: %v", install.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
newEnvFile := ""
|
||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
for _, resource := range resources {
|
||||
|
|
@ -348,7 +348,7 @@ func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverF
|
|||
}
|
||||
if !isOk {
|
||||
t.Log(i18n.GetMsgByKey("RecoverFailedStartRollBack"))
|
||||
if err := handleAppRecover(install, t, rollbackFile, true, secret, ""); err != nil {
|
||||
if err := handleAppRecover(install, t, rollbackFile, true, "", ""); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
|
|||
var list []backupSizeHelper
|
||||
switch req.Type {
|
||||
case "snapshot":
|
||||
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -214,7 +214,7 @@ func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.Recor
|
|||
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)})
|
||||
}
|
||||
case "cronjob":
|
||||
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, backupRepo.WithByCronID(req.CronjobID))
|
||||
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderBy("created_at desc"), backupRepo.WithByCronID(req.CronjobID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,25 @@ func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string, exc
|
|||
|
||||
func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback bool, secret string) error {
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile), "", secret); err != nil {
|
||||
return fmt.Errorf("backup runtime %s for rollback before recover failed, err: %v", runtime.Name, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := handleRuntimeRecover(runtime, rollbackFile, true, ""); err != nil {
|
||||
global.LOG.Errorf("rollback runtime %s from %s failed, err: %v", runtime.Name, rollbackFile, err)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("rollback runtime %s from %s successful", runtime.Name, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||
return err
|
||||
|
|
@ -71,26 +90,6 @@ func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback
|
|||
return errors.New("the current backup file does not match the application")
|
||||
}
|
||||
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile), "", secret); err != nil {
|
||||
return fmt.Errorf("backup runtime %s for rollback before recover failed, err: %v", runtime.Name, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
global.LOG.Info("recover failed, start to rollback now")
|
||||
if err := handleRuntimeRecover(runtime, rollbackFile, true, secret); err != nil {
|
||||
global.LOG.Errorf("rollback runtime %s from %s failed, err: %v", runtime.Name, rollbackFile, err)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("rollback runtime %s from %s successful", runtime.Name, rollbackFile)
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
newEnvFile, err := coverEnvJsonToStr(runtime.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -78,6 +78,26 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
|||
return err
|
||||
}
|
||||
recoverTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite), func(t *task.Task) error {
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
t.LogStart(i18n.GetMsgByKey("Rollback"))
|
||||
if err := handleWebsiteRecover(website, rollbackFile, true, "", taskID); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||
return
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("Rollback"))
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||
t.Log(i18n.GetWithName("DeCompressFile", recoverFile))
|
||||
|
|
@ -112,27 +132,6 @@ func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback
|
|||
}
|
||||
}
|
||||
|
||||
isOk := false
|
||||
if !isRollback {
|
||||
rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||
}
|
||||
defer func() {
|
||||
if !isOk {
|
||||
t.LogStart(i18n.GetMsgByKey("Rollback"))
|
||||
if err := handleWebsiteRecover(website, rollbackFile, true, "", taskID); err != nil {
|
||||
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||
return
|
||||
}
|
||||
t.LogSuccess(i18n.GetMsgByKey("Rollback"))
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
} else {
|
||||
_ = os.RemoveAll(rollbackFile)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
|
|||
|
||||
fileOp := files.NewFileOp()
|
||||
if cronjob.IsDir {
|
||||
if err := fileOp.TarGzCompressPro(true, cronjob.SourceDir, path.Join(backupDir, fileName), cronjob.ExclusionRules, cronjob.Secret); err != nil {
|
||||
if err := fileOp.TarGzCompressPro(true, cronjob.SourceDir, path.Join(backupDir, fileName), cronjob.Secret, cronjob.ExclusionRules); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -54,8 +54,6 @@ const openWithTaskID = (id: string, tail: boolean) => {
|
|||
}
|
||||
open.value = true;
|
||||
bus.emit('refreshTask', true);
|
||||
|
||||
console.log('11111');
|
||||
};
|
||||
|
||||
const openWithResourceID = (taskType: string, taskOperate: string, resourceID: number) => {
|
||||
|
|
|
|||
|
|
@ -3126,7 +3126,6 @@ const message = {
|
|||
favicon: 'Website Icon',
|
||||
faviconHelper: 'Website icon (recommended image size: 16px*16px)',
|
||||
reUpload: 'Choose File',
|
||||
supportType: 'Only JPG, PNG, JPEG, and GIF files are supported.',
|
||||
setDefault: 'Restore Default',
|
||||
setHelper: 'The current settings will be saved. Do you want to continue?',
|
||||
setDefaultHelper: 'All panel settings will be restored to default. Do you want to continue?',
|
||||
|
|
|
|||
|
|
@ -2935,7 +2935,6 @@ const message = {
|
|||
favicon: 'ウェブサイトアイコン',
|
||||
faviconHelper: 'ウェブサイトアイコン(推奨画像サイズ:16px*16px)',
|
||||
reUpload: 'ファイルを選択',
|
||||
supportType: 'JPG、PNG、JPEG、GIFファイルのみサポートされています。',
|
||||
setDefault: 'デフォルトに戻す',
|
||||
setHelper: '現在の設定が保存されます。続けますか?',
|
||||
setDefaultHelper: 'すべてのパネル設定がデフォルトに戻されます。続けますか?',
|
||||
|
|
|
|||
|
|
@ -2890,7 +2890,6 @@ const message = {
|
|||
favicon: '웹사이트 아이콘',
|
||||
faviconHelper: '웹사이트 아이콘 (권장 이미지 크기: 16px*16px)',
|
||||
reUpload: '파일 선택',
|
||||
supportType: 'JPG, PNG, JPEG, 및 GIF 파일만 지원됩니다.',
|
||||
setDefault: '기본값 복원',
|
||||
setHelper: '현재 설정이 저장됩니다. 계속하시겠습니까?',
|
||||
setDefaultHelper: '모든 패널 설정이 기본값으로 복원됩니다. 계속하시겠습니까?',
|
||||
|
|
|
|||
|
|
@ -3005,7 +3005,6 @@ const message = {
|
|||
favicon: 'Ikon Laman Web',
|
||||
faviconHelper: 'Ikon laman web (saiz imej yang disarankan: 16px*16px)',
|
||||
reUpload: 'Pilih Fail',
|
||||
supportType: 'Hanya fail JPG, PNG, JPEG, dan GIF yang disokong.',
|
||||
setDefault: 'Pulihkan Tetapan Asal',
|
||||
setHelper: 'Tetapan semasa akan disimpan. Adakah anda ingin meneruskan?',
|
||||
setDefaultHelper: 'Semua tetapan panel akan dikembalikan ke asal. Adakah anda ingin meneruskan?',
|
||||
|
|
|
|||
|
|
@ -3008,7 +3008,6 @@ const message = {
|
|||
favicon: 'Ícone do Site',
|
||||
faviconHelper: 'Ícone do site (tamanho recomendado da imagem: 16px*16px)',
|
||||
reUpload: 'Selecionar Arquivo',
|
||||
supportType: 'Apenas arquivos JPG, PNG, JPEG e GIF são suportados.',
|
||||
setDefault: 'Restaurar Padrão',
|
||||
setHelper: 'As configurações atuais serão salvas. Deseja continuar?',
|
||||
setDefaultHelper: 'Todas as configurações do painel serão restauradas para o padrão. Deseja continuar?',
|
||||
|
|
|
|||
|
|
@ -2998,7 +2998,6 @@ const message = {
|
|||
favicon: 'Иконка Сайта',
|
||||
faviconHelper: 'Иконка сайта (рекомендуемый размер изображения: 16px*16px)',
|
||||
reUpload: 'Выбрать Файл',
|
||||
supportType: 'Поддерживаются только файлы JPG, PNG, JPEG и GIF.',
|
||||
setDefault: 'Восстановить По Умолчанию',
|
||||
setHelper: 'Текущие настройки будут сохранены. Вы хотите продолжить?',
|
||||
setDefaultHelper: 'Все настройки панели будут восстановлены по умолчанию. Вы хотите продолжить?',
|
||||
|
|
|
|||
|
|
@ -2897,7 +2897,6 @@ const message = {
|
|||
favicon: '網站圖標',
|
||||
faviconHelper: '網站圖標 (建議圖片大小為: 16px*16px)',
|
||||
reUpload: '選擇文件',
|
||||
supportType: '只能上傳 jpg/png/jpeg/gif 文件!',
|
||||
setDefault: '復原預設',
|
||||
setHelper: '即將儲存目前介面設定內容,是否繼續?',
|
||||
setDefaultHelper: '即將復原所有界面設定到初始狀態,是否繼續?',
|
||||
|
|
|
|||
|
|
@ -2880,7 +2880,6 @@ const message = {
|
|||
favicon: '网站图标',
|
||||
faviconHelper: '网站图标 (建议图片大小为: 16px*16px)',
|
||||
reUpload: '选择文件',
|
||||
supportType: '只能上传 jpg/png/jpeg/gif 文件!',
|
||||
setHelper: '即将保存当前界面设置内容,是否继续?',
|
||||
setDefaultHelper: '即将恢复所有界面设置到初始状态,是否继续?',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -101,8 +101,9 @@
|
|||
</el-select>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</template>
|
||||
<template #main v-if="currentDB">
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
v-if="currentDB"
|
||||
:pagination-config="paginationConfig"
|
||||
:class="{ mask: maskShow }"
|
||||
@sort-change="search"
|
||||
|
|
@ -205,6 +206,18 @@
|
|||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
<div v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0" class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noMysql')]) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
|
|
@ -217,25 +230,6 @@
|
|||
</span>
|
||||
</el-card>
|
||||
|
||||
<div v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<LayoutContent :title="'MySQL ' + $t('menu.database')" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noMysql')]) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
|
||||
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
|
||||
<div class="flex justify-center items-center gap-2 flex-wrap">
|
||||
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
|
||||
|
|
@ -509,7 +503,7 @@ const loadDBOptions = async () => {
|
|||
}
|
||||
}
|
||||
if (currentDB.value) {
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (currentDB.value?.from === 'remote') {
|
||||
maskShow.value = false;
|
||||
}
|
||||
globalStore.setCurrentDB('');
|
||||
|
|
@ -529,7 +523,7 @@ const loadDBOptions = async () => {
|
|||
if (currentDB.value) {
|
||||
search();
|
||||
}
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (currentDB.value?.from === 'remote') {
|
||||
maskShow.value = false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,8 +78,9 @@
|
|||
</el-select>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</template>
|
||||
<template #main v-if="currentDB">
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
v-if="currentDB"
|
||||
:class="{ mask: maskShow }"
|
||||
:pagination-config="paginationConfig"
|
||||
@sort-change="search"
|
||||
|
|
@ -171,6 +172,18 @@
|
|||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
<div class="app-warn" v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noPostgresql')]) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
|
|
@ -181,25 +194,6 @@
|
|||
<span>{{ $t('commons.service.serviceNotStarted', ['PostgreSQL']) }}</span>
|
||||
</el-card>
|
||||
|
||||
<div v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<LayoutContent :title="'PostgreSQL ' + $t('menu.database')" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', [$t('database.noPostgresql')]) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
|
||||
<DialogPro v-model="open" :title="$t('app.checkTitle')" size="small">
|
||||
<div class="flex justify-center items-center gap-2 flex-wrap">
|
||||
{{ $t('app.checkInstalledWarn', [dashboardName]) }}
|
||||
|
|
@ -468,7 +462,7 @@ const loadDBOptions = async () => {
|
|||
}
|
||||
}
|
||||
if (currentDB.value) {
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (currentDB.value?.from === 'remote') {
|
||||
maskShow.value = false;
|
||||
}
|
||||
globalStore.setCurrentDB('');
|
||||
|
|
@ -488,7 +482,7 @@ const loadDBOptions = async () => {
|
|||
if (currentDB.value) {
|
||||
search();
|
||||
}
|
||||
if (currentDB.value.from === 'remote') {
|
||||
if (currentDB.value?.from === 'remote') {
|
||||
maskShow.value = false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -95,23 +95,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<LayoutContent :title="'Redis ' + $t('menu.database')" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', ['Redis']) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<div class="app-warn" v-if="dbOptionsLocal.length === 0 && dbOptionsRemote.length === 0">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('app.checkInstalledWarn', ['Redis']) }}</span>
|
||||
<span @click="goRouter('app')" class="flex items-center justify-center gap-0.5">
|
||||
<el-icon><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue