mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 增加基础应用组件
This commit is contained in:
parent
e56f54f30f
commit
ff2b4d030e
@ -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")
|
key, ok := c.Params.Get("key")
|
||||||
if !ok {
|
if !ok {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error key in path"))
|
||||||
|
@ -2,6 +2,7 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
)
|
)
|
||||||
@ -42,7 +43,12 @@ type AppInstallRequest struct {
|
|||||||
type CheckInstalled struct {
|
type CheckInstalled struct {
|
||||||
IsExist bool `json:"isExist"`
|
IsExist bool `json:"isExist"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
App string `json:"app"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
LastBackupAt string `json:"lastBackupAt"`
|
||||||
|
AppInstallID uint `json:"appInstallId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppInstalled struct {
|
type AppInstalled struct {
|
||||||
|
@ -54,7 +54,10 @@ func (a AppInstallRepo) GetBy(opts ...DBOption) ([]model.AppInstall, error) {
|
|||||||
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
|
func (a AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) {
|
||||||
var install model.AppInstall
|
var install model.AppInstall
|
||||||
db := getDb(opts...).Model(&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
|
return install, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,11 +76,10 @@ func getTx(ctx context.Context, opts ...DBOption) *gorm.DB {
|
|||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
tx = opt(tx)
|
tx = opt(tx)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return getDb(opts...)
|
|
||||||
}
|
|
||||||
return tx
|
return tx
|
||||||
}
|
}
|
||||||
|
return getDb(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
func getDb(opts ...DBOption) *gorm.DB {
|
func getDb(opts ...DBOption) *gorm.DB {
|
||||||
db := global.DB
|
db := global.DB
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -38,15 +39,29 @@ func (a AppInstallService) Page(req dto.AppInstalledRequest) (int64, []dto.AppIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a AppInstallService) CheckExist(key string) (*dto.CheckInstalled, error) {
|
func (a AppInstallService) CheckExist(key string) (*dto.CheckInstalled, error) {
|
||||||
|
res := &dto.CheckInstalled{
|
||||||
|
IsExist: false,
|
||||||
|
}
|
||||||
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
app, err := appRepo.GetFirst(appRepo.WithKey(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &dto.CheckInstalled{IsExist: false}, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
|
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID))
|
||||||
if appInstall.ID != 0 {
|
if reflect.DeepEqual(appInstall, model.AppInstall{}) {
|
||||||
return &dto.CheckInstalled{Name: appInstall.Name, IsExist: true, Version: appInstall.Version}, nil
|
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) {
|
func (a AppInstallService) Search(req dto.AppInstalledRequest) ([]dto.AppInstalled, error) {
|
||||||
|
@ -21,7 +21,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
|||||||
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
|
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
|
||||||
appRouter.POST("/install", baseApi.InstallApp)
|
appRouter.POST("/install", baseApi.InstallApp)
|
||||||
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
|
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", baseApi.SearchAppInstalled)
|
||||||
appRouter.POST("/installed/op", baseApi.OperateInstalled)
|
appRouter.POST("/installed/op", baseApi.OperateInstalled)
|
||||||
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
|
appRouter.POST("/installed/sync", baseApi.SyncInstalled)
|
||||||
|
@ -86,6 +86,11 @@ export namespace App {
|
|||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
isExist: boolean;
|
isExist: boolean;
|
||||||
|
app: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
lastBackupAt: string;
|
||||||
|
appInstallId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppInstalledOp {
|
export interface AppInstalledOp {
|
||||||
|
141
frontend/src/components/app-status/index.vue
Normal file
141
frontend/src/components/app-status/index.vue
Normal 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>
|
@ -687,8 +687,11 @@ export default {
|
|||||||
update: '升级',
|
update: '升级',
|
||||||
versioneSelect: '请选择版本',
|
versioneSelect: '请选择版本',
|
||||||
operatorHelper: '将对选中应用进行 {0} 操作,是否继续?',
|
operatorHelper: '将对选中应用进行 {0} 操作,是否继续?',
|
||||||
|
checkInstalledWarn: '未检测到',
|
||||||
|
gotoInstalled: '去安装',
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
|
website: '网站',
|
||||||
primaryDomain: '主域名',
|
primaryDomain: '主域名',
|
||||||
otherDomains: '其他域名',
|
otherDomains: '其他域名',
|
||||||
type: '类型',
|
type: '类型',
|
||||||
@ -747,5 +750,7 @@ export default {
|
|||||||
config: '配置',
|
config: '配置',
|
||||||
enableHTTPS: '启用HTTPS',
|
enableHTTPS: '启用HTTPS',
|
||||||
aliasHelper: '代号是网站目录的文件夹名称',
|
aliasHelper: '代号是网站目录的文件夹名称',
|
||||||
|
lastBackupAt: '上次备份时间',
|
||||||
|
null: '无',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -18,15 +18,16 @@
|
|||||||
|
|
||||||
/* 防止切换出现横向滚动条 */
|
/* 防止切换出现横向滚动条 */
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: #f0f2f5;
|
// background-color: #f0f2f5;
|
||||||
|
// background: #f0f2f5;
|
||||||
.main-box {
|
.main-box {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 15px;
|
padding: 5px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
background-color: #ffffff;
|
// background-color: #f0f2f5;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
// box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
@ -57,13 +57,6 @@
|
|||||||
/>
|
/>
|
||||||
</ComplexTable>
|
</ComplexTable>
|
||||||
<el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%">
|
<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">
|
<div style="text-align: center">
|
||||||
<p>{{ $t('app.versioneSelect') }}</p>
|
<p>{{ $t('app.versioneSelect') }}</p>
|
||||||
<el-select v-model="operateReq.detailId">
|
<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 = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
label: i18n.global.t('app.sync'),
|
label: i18n.global.t('app.sync'),
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutContent :header="'网站'">
|
<div ref="websiteRef">
|
||||||
|
<LayoutContent>
|
||||||
|
<AppStatus :app-key="'nginx'" :parentRef="websiteRef"></AppStatus>
|
||||||
|
<br />
|
||||||
|
<el-card>
|
||||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search()">
|
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search()">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<el-button type="primary" plain @click="openCreate">{{ $t('commons.button.create') }}</el-button>
|
<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 @click="openGroup">{{ $t('website.group') }}</el-button>
|
||||||
<!-- <el-button type="primary" plain>{{ '修改默认页' }}</el-button>
|
<!-- <el-button type="primary" plain>{{ '修改默认页' }}</el-button>
|
||||||
<el-button type="primary" plain>{{ '默认站点' }}</el-button> -->
|
<el-button type="primary" plain>{{ '默认站点' }}</el-button> -->
|
||||||
@ -24,10 +30,13 @@
|
|||||||
fix
|
fix
|
||||||
/>
|
/>
|
||||||
</ComplexTable>
|
</ComplexTable>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<CreateWebSite ref="createRef" @close="search"></CreateWebSite>
|
<CreateWebSite ref="createRef" @close="search"></CreateWebSite>
|
||||||
<DeleteWebsite ref="deleteRef" @close="search"></DeleteWebsite>
|
<DeleteWebsite ref="deleteRef" @close="search"></DeleteWebsite>
|
||||||
<WebSiteGroup ref="groupRef"></WebSiteGroup>
|
<WebSiteGroup ref="groupRef"></WebSiteGroup>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -39,6 +48,7 @@ import DeleteWebsite from './delete/index.vue';
|
|||||||
import WebSiteGroup from './group/index.vue';
|
import WebSiteGroup from './group/index.vue';
|
||||||
import { SearchWebSites } from '@/api/modules/website';
|
import { SearchWebSites } from '@/api/modules/website';
|
||||||
import { WebSite } from '@/api/interface/website';
|
import { WebSite } from '@/api/interface/website';
|
||||||
|
import AppStatus from '@/components/app-status/index.vue';
|
||||||
|
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
@ -46,6 +56,7 @@ import router from '@/routers';
|
|||||||
const createRef = ref();
|
const createRef = ref();
|
||||||
const deleteRef = ref();
|
const deleteRef = ref();
|
||||||
const groupRef = ref();
|
const groupRef = ref();
|
||||||
|
const websiteRef = ref();
|
||||||
|
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user