feat: 增加基础应用组件

This commit is contained in:
zhengkunwang223 2022-11-22 22:47:38 +08:00 committed by zhengkunwang223
parent e56f54f30f
commit ff2b4d030e
12 changed files with 230 additions and 74 deletions

View File

@ -39,7 +39,7 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
}
}
func (b *BaseApi) CheckAppInstalld(c *gin.Context) {
func (b *BaseApi) CheckAppInstalled(c *gin.Context) {
key, ok := c.Params.Get("key")
if !ok {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))

View File

@ -2,6 +2,7 @@ package dto
import (
"encoding/json"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
@ -40,9 +41,14 @@ type AppInstallRequest struct {
}
type CheckInstalled struct {
IsExist bool `json:"isExist"`
Name string `json:"name"`
Version string `json:"version"`
IsExist bool `json:"isExist"`
Name string `json:"name"`
App string `json:"app"`
Version string `json:"version"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
LastBackupAt string `json:"lastBackupAt"`
AppInstallID uint `json:"appInstallId"`
}
type AppInstalled struct {

View File

@ -54,7 +54,10 @@ func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
var install model.AppInstall
db := getDb(opts...).Model(&model.AppInstall{})
err := db.Preload("App").Preload("Backups").First(&install).Error
err := db.Preload("App").Preload("Backups", func(db *gorm.DB) *gorm.DB {
db = db.Order("created_at desc")
return db
}).First(&install).Error
return install, err
}

View File

@ -76,10 +76,9 @@ func getTx(ctx context.Context, opts ...DBOption) *gorm.DB {
for _, opt := range opts {
tx = opt(tx)
}
} else {
return getDb(opts...)
return tx
}
return tx
return getDb(opts...)
}
func getDb(opts ...DBOption) *gorm.DB {

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path"
"reflect"
"strconv"
"strings"
@ -38,15 +39,29 @@ func (a AppInstallService) Page(req dto.AppInstalledRequest) (int64, []dto.AppIn
}
func (a AppInstallService) CheckExist(key string) (*dto.CheckInstalled, error) {
res := &dto.CheckInstalled{
IsExist: false,
}
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return &dto.CheckInstalled{IsExist: false}, nil
return res, nil
}
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
if appInstall.ID != 0 {
return &dto.CheckInstalled{Name: appInstall.Name, IsExist: true, Version: appInstall.Version}, nil
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
return res, nil
}
return &dto.CheckInstalled{IsExist: false}, nil
res.Name = appInstall.Name
res.App = app.Name
res.Version = appInstall.Version
res.CreatedAt = appInstall.CreatedAt
res.Status = appInstall.Status
res.AppInstallID = appInstall.ID
res.IsExist = true
if len(appInstall.Backups) > 0 {
res.LastBackupAt = appInstall.Backups[0].CreatedAt.Format("2006-01-02 15:04:05")
}
return res, nil
}
func (a AppInstallService) Search(req dto.AppInstalledRequest) ([]dto.AppInstalled, error) {

View File

@ -21,7 +21,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
appRouter.GET("/installed/check/:key", baseApi.CheckAppInstalld)
appRouter.GET("/installed/check/:key", baseApi.CheckAppInstalled)
appRouter.POST("/installed", baseApi.SearchAppInstalled)
appRouter.POST("/installed/op", baseApi.OperateInstalled)
appRouter.POST("/installed/sync", baseApi.SyncInstalled)

View File

@ -86,6 +86,11 @@ export namespace App {
name: string;
version: string;
isExist: boolean;
app: string;
status: string;
createdAt: string;
lastBackupAt: string;
appInstallId: number;
}
export interface AppInstalledOp {

View File

@ -0,0 +1,141 @@
<template>
<div>
<el-card class="app-card" v-loading="loading">
<div class="app-content" v-if="data.isExist">
<el-row :gutter="20">
<el-col :span="1">
<div>
<el-tag effect="dark" type="success">{{ data.app }}</el-tag>
</div>
</el-col>
<el-col :span="2">
<div>
{{ $t('app.version') }}:
<el-tag type="info">{{ data.version }}</el-tag>
</div>
</el-col>
<el-col :span="2">
<div>
{{ $t('commons.table.status') }}:
<el-tag type="success">{{ data.status }}</el-tag>
</div>
</el-col>
<el-col :span="4">
<div>
{{ $t('website.lastBackupAt') }}:
<el-tag v-if="data.lastBackupAt != ''" type="info">{{ data.lastBackupAt }}</el-tag>
<span else>{{ $t('website.null') }}</span>
</div>
</el-col>
<el-col :span="6">
<el-button type="primary" link @click="onOperate('restart')">{{ $t('app.restart') }}</el-button>
<el-button type="primary" link @click="setting">{{ $t('commons.button.set') }}</el-button>
</el-col>
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="2">
{{ $t('app.checkInstalledWarn', [data.app]) }}
<el-tag effect="dark" type="success">{{ data.app }}</el-tag>
</el-col>
<el-col :span="3">
<el-link icon="Position" @click="goRouter('/apps')" type="primary">
{{ $t('database.goInstall') }}
</el-link>
</el-col>
</el-row>
<span></span>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { CheckAppInstalled, InstalledOp } from '@/api/modules/app';
import i18n from '@/lang';
import router from '@/routers';
import { ElMessage, ElMessageBox } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
const props = defineProps({
appKey: {
type: String,
default: 'nginx',
},
});
let key = ref('');
let data = ref({
app: '',
version: '',
status: '',
lastBackupAt: '',
appInstallId: 0,
isExist: false,
});
let loading = ref(false);
let operateReq = reactive({
installId: 0,
operate: '',
});
const em = defineEmits(['setting']);
const setting = () => {
em('setting', false);
};
const goRouter = async (path: string) => {
router.push({ path: path });
};
const onCheck = async () => {
loading.value = true;
const res = await CheckAppInstalled(key.value);
data.value = res.data;
operateReq.installId = res.data.appInstallId;
loading.value = false;
};
const onOperate = async (operation: string) => {
operateReq.operate = operation;
ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('app.' + operation)]),
i18n.global.t('app.' + operation),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(() => {
loading.value = true;
InstalledOp(operateReq)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
onCheck();
})
.finally(() => {
loading.value = false;
});
});
};
onMounted(() => {
key.value = props.appKey;
onCheck();
});
</script>
<style lang="scss">
.app-card {
font-size: 14px;
height: 60px;
}
.app-content {
height: 50px;
}
body {
margin: 0;
}
</style>

View File

@ -687,8 +687,11 @@ export default {
update: '升级',
versioneSelect: '请选择版本',
operatorHelper: '将对选中应用进行 {0} 操作是否继续',
checkInstalledWarn: '未检测到',
gotoInstalled: '去安装',
},
website: {
website: '网站',
primaryDomain: '主域名',
otherDomains: '其他域名',
type: '类型',
@ -747,5 +750,7 @@ export default {
config: '配置',
enableHTTPS: '启用HTTPS',
aliasHelper: '代号是网站目录的文件夹名称',
lastBackupAt: '上次备份时间',
null: '无',
},
};

View File

@ -18,15 +18,16 @@
/* 防止切换出现横向滚动条 */
overflow-x: hidden;
background: #f0f2f5;
// background-color: #f0f2f5;
// background: #f0f2f5;
.main-box {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 15px;
padding: 5px;
overflow: auto;
overflow-x: hidden !important;
background-color: #ffffff;
// background-color: #f0f2f5;
border-radius: 4px;
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
&::-webkit-scrollbar {

View File

@ -57,13 +57,6 @@
/>
</ComplexTable>
<el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%">
<!-- <el-alert
v-if="operateReq.operate != 'update'"
:title="getMsg(operateReq.operate)"
type="warning"
:closable="false"
show-icon
/> -->
<div style="text-align: center">
<p>{{ $t('app.versioneSelect') }}</p>
<el-select v-model="operateReq.detailId">
@ -198,29 +191,6 @@ const onOperate = async (operation: string) => {
});
};
// const getMsg = (op: string) => {
// let tip = '';
// switch (op) {
// case 'up':
// tip = i18n.global.t('app.up');
// break;
// case 'down':
// tip = i18n.global.t('app.down');
// break;
// case 'restart':
// tip = i18n.global.t('app.restart');
// break;
// case 'delete':
// tip = i18n.global.t('app.deleteWarn');
// break;
// case 'sync':
// tip = i18n.global.t('app.sync');
// break;
// default:
// }
// return tip;
// };
const buttons = [
{
label: i18n.global.t('app.sync'),

View File

@ -1,33 +1,42 @@
<template>
<LayoutContent :header="'网站'">
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">{{ $t('commons.button.create') }}</el-button>
<el-button type="primary" plain @click="openGroup">{{ $t('website.group') }}</el-button>
<!-- <el-button type="primary" plain>{{ '修改默认页' }}</el-button>
<div ref="websiteRef">
<LayoutContent>
<AppStatus :app-key="'nginx'" :parentRef="websiteRef"></AppStatus>
<br />
<el-card>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">
{{ $t('commons.button.create') }}
</el-button>
<el-button type="primary" plain @click="openGroup">{{ $t('website.group') }}</el-button>
<!-- <el-button type="primary" plain>{{ '修改默认页' }}</el-button>
<el-button type="primary" plain>{{ '默认站点' }}</el-button> -->
</template>
<el-table-column :label="$t('commons.table.name')" fix show-overflow-tooltip prop="primaryDomain">
<template #default="{ row }">
<el-link @click="openConfig(row.id)">{{ row.primaryDomain }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status"></el-table-column>
<!-- <el-table-column :label="'备份'" prop="backup"></el-table-column> -->
<el-table-column :label="'备注'" prop="remark"></el-table-column>
<!-- <el-table-column :label="'SSL证书'" prop="ssl"></el-table-column> -->
<fu-table-operations
:ellipsis="1"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<CreateWebSite ref="createRef" @close="search"></CreateWebSite>
<DeleteWebsite ref="deleteRef" @close="search"></DeleteWebsite>
<WebSiteGroup ref="groupRef"></WebSiteGroup>
</LayoutContent>
</template>
<el-table-column :label="$t('commons.table.name')" fix show-overflow-tooltip prop="primaryDomain">
<template #default="{ row }">
<el-link @click="openConfig(row.id)">{{ row.primaryDomain }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status"></el-table-column>
<!-- <el-table-column :label="'备份'" prop="backup"></el-table-column> -->
<el-table-column :label="'备注'" prop="remark"></el-table-column>
<!-- <el-table-column :label="'SSL证书'" prop="ssl"></el-table-column> -->
<fu-table-operations
:ellipsis="1"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
</el-card>
<CreateWebSite ref="createRef" @close="search"></CreateWebSite>
<DeleteWebsite ref="deleteRef" @close="search"></DeleteWebsite>
<WebSiteGroup ref="groupRef"></WebSiteGroup>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
@ -39,6 +48,7 @@ import DeleteWebsite from './delete/index.vue';
import WebSiteGroup from './group/index.vue';
import { SearchWebSites } from '@/api/modules/website';
import { WebSite } from '@/api/interface/website';
import AppStatus from '@/components/app-status/index.vue';
import i18n from '@/lang';
import router from '@/routers';
@ -46,6 +56,7 @@ import router from '@/routers';
const createRef = ref();
const deleteRef = ref();
const groupRef = ref();
const websiteRef = ref();
const paginationConfig = reactive({
currentPage: 1,