feat: 菜单增加运行环境

This commit is contained in:
zhengkunwang223 2023-03-29 14:58:28 +08:00 committed by zhengkunwang223
parent 8be00dad7f
commit 1949be2490
19 changed files with 318 additions and 1 deletions

View file

@ -0,0 +1,34 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags Runtime
// @Summary List runtimes
// @Description 获取运行环境列表
// @Accept json
// @Param request body request.RuntimeSearch true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/search [post]
func (b *BaseApi) SearchRuntimes(c *gin.Context) {
var req request.RuntimeSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, items, err := runtimeService.Page(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: items,
})
}

View file

@ -0,0 +1,9 @@
package request
import "github.com/1Panel-dev/1Panel/backend/app/dto"
type RuntimeSearch struct {
dto.PageInfo
Type string `json:"type"`
Name string `json:"name"`
}

View file

@ -0,0 +1,7 @@
package response
import "github.com/1Panel-dev/1Panel/backend/app/model"
type RuntimeRes struct {
model.Runtime
}

View file

@ -0,0 +1,13 @@
package model
type Runtime struct {
BaseModel
Name string `gorm:"type:varchar;not null" json:"name"`
AppDetailID uint `gorm:"type:integer" json:"appDetailId"`
Image string `gorm:"type:varchar;not null" json:"image"`
WorkDir string `gorm:"type:varchar;not null" json:"workDir"`
DockerCompose string `gorm:"type:varchar;not null" json:"dockerCompose"`
Env string `gorm:"type:varchar;not null" json:"env"`
Params string `gorm:"type:varchar;not null" json:"params"`
Type string `gorm:"type:varchar;not null" json:"type"`
}

View file

@ -0,0 +1,42 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type RuntimeRepo struct {
}
type IRuntimeRepo interface {
Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error)
Create(ctx context.Context, runtime *model.Runtime) error
Save(runtime *model.Runtime) error
DeleteBy(opts ...DBOption) error
}
func NewIRunTimeRepo() IRuntimeRepo {
return &RuntimeRepo{}
}
func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) {
var runtimes []model.Runtime
db := getDb(opts...).Model(&model.Runtime{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&runtimes).Error
return count, runtimes, err
}
func (r *RuntimeRepo) Create(ctx context.Context, runtime *model.Runtime) error {
db := getTx(ctx).Model(&model.Runtime{})
return db.Create(&runtime).Error
}
func (r *RuntimeRepo) Save(runtime *model.Runtime) error {
return getDb().Save(&runtime).Error
}
func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.Runtime{}).Error
}

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/subosito/gotenv"
"math"
"os"
"path"
@ -224,7 +225,11 @@ func updateInstall(installId uint, detailId uint) error {
}
func getContainerNames(install model.AppInstall) ([]string, error) {
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(install.Env))
envStr, err := coverEnvJsonToStr(install.Env)
if err != nil {
return nil, err
}
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr))
if err != nil {
return nil, err
}
@ -238,6 +243,18 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
return containerNames, nil
}
func coverEnvJsonToStr(envJson string) (string, error) {
envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(envJson), &envMap)
newEnvMap := make(map[string]string, len(envMap))
handleMap(envMap, newEnvMap)
envStr, err := gotenv.Marshal(newEnvMap)
if err != nil {
return "", err
}
return envStr, nil
}
func checkLimit(app model.App) error {
if app.Limit > 0 {
installs, err := appInstallRepo.ListBy(appInstallRepo.WithAppId(app.ID))

View file

@ -0,0 +1,42 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/repo"
)
type RuntimeService struct {
}
type IRuntimeService interface {
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
}
func NewRuntimeService() IRuntimeService {
return &RuntimeService{}
}
func (r *RuntimeService) Create() {
}
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {
var (
opts []repo.DBOption
res []response.RuntimeRes
)
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
}
total, runtimes, err := runtimeRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
for _, runtime := range runtimes {
res = append(res, response.RuntimeRes{
Runtime: runtime,
})
}
return total, res, nil
}

View file

@ -11,4 +11,5 @@ var (
ResourceDir = path.Join(DataDir, "resource")
AppResourceDir = path.Join(ResourceDir, "apps")
AppInstallDir = path.Join(DataDir, "apps")
RuntimeDir = path.Join(DataDir, "runtime")
)

View file

@ -21,6 +21,7 @@ func Init() {
migrations.AddTableDatabaseMysql,
migrations.AddTableSnap,
migrations.AddDefaultGroup,
migrations.AddTableRuntime,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -247,3 +247,10 @@ var AddDefaultGroup = &gormigrate.Migration{
return tx.Migrator().DropTable("website_groups")
},
}
var AddTableRuntime = &gormigrate.Migration{
ID: "20230328-add-table-runtime",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Runtime{})
},
}

View file

@ -96,6 +96,7 @@ func Routers() *gin.Engine {
systemRouter.InitWebsiteSSLRouter(PrivateGroup)
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
systemRouter.InitNginxRouter(PrivateGroup)
systemRouter.InitRuntimeRouter(PrivateGroup)
}
return Router

View file

@ -19,6 +19,7 @@ type RouterGroup struct {
WebsiteSSLRouter
DatabaseRouter
NginxRouter
RuntimeRouter
}
var RouterGroupApp = new(RouterGroup)

View file

@ -0,0 +1,20 @@
package router
import (
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
"github.com/1Panel-dev/1Panel/backend/middleware"
"github.com/gin-gonic/gin"
)
type RuntimeRouter struct {
}
func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("runtimes")
groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("/search", baseApi.SearchRuntimes)
}
}

View file

@ -0,0 +1,21 @@
import { CommonModel, ReqPage } from '.';
export namespace Runtime {
export interface Runtime extends CommonModel {
name: string;
appDetailId: string;
image: string;
workDir: string;
dockerCompose: string;
env: string;
params: string;
type: string;
}
export interface RuntimeReq extends ReqPage {
name: string;
}
export interface RuntimeDTO extends Runtime {
websites: string[];
}
}

View file

@ -0,0 +1,7 @@
import http from '@/api';
import { ResPage } from '../interface';
import { Runtime } from '../interface/runtime';
export const SearchRuntimes = (req: Runtime.RuntimeReq) => {
return http.post<ResPage<Runtime.RuntimeDTO>>(`/runtimes/search`, req);
};

View file

@ -220,6 +220,7 @@ const message = {
toolbox: 'Toolbox',
logs: 'Log',
ssl: 'Certificate',
runtime: 'Runtime',
},
home: {
overview: 'Overview',
@ -1233,6 +1234,11 @@ const message = {
portRule: 'Port rule',
ipRule: 'IP rule',
},
runtime: {
runtime: 'Runtime',
image: 'Image',
workDir: 'WorkDir',
},
};
export default {

View file

@ -223,6 +223,7 @@ const message = {
settings: '面板设置',
toolbox: '工具箱',
logs: '面板日志',
runtime: '运行环境',
},
home: {
overview: '概览',
@ -1223,6 +1224,11 @@ const message = {
portRule: '端口规则',
ipRule: 'IP 规则',
},
runtime: {
runtime: '运行环境',
image: '镜像',
workDir: '工作目录',
},
};
export default {
...fit2cloudZhLocale,

View file

@ -39,6 +39,15 @@ const webSiteRouter = {
requiresAuth: false,
},
},
{
path: '/websites/runtime',
name: 'Runtime',
component: () => import('@/views/website/runtime/index.vue'),
meta: {
title: 'menu.runtime',
requiresAuth: false,
},
},
],
};

View file

@ -0,0 +1,73 @@
<template>
<div>
<RouterButton
:buttons="[
{
label: 'PHP',
path: '/runtimes',
},
]"
/>
<LayoutContent :title="$t('runtime.runtime')" v-loading="loading">
<template #toolbar></template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
<el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px">
<template #default="{ row }">
<Tooltip :text="row.name" />
</template>
</el-table-column>
<el-table-column :label="$t('runtime.image')" prop="image"></el-table-column>
<el-table-column :label="$t('runtime.workDir')" prop="workDir"></el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFormat"
show-overflow-tooltip
/>
</ComplexTable>
</template>
</LayoutContent>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { SearchRuntimes } from '@/api/modules/runtime';
import RouterButton from '@/components/router-button/index.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import LayoutContent from '@/layout/layout-content.vue';
import { dateFormat } from '@/utils/util';
const paginationConfig = reactive({
currentPage: 1,
pageSize: 15,
total: 0,
});
let req = reactive<Runtime.RuntimeReq>({
name: '',
page: 1,
pageSize: 15,
});
const loading = ref(false);
const items = ref<Runtime.RuntimeDTO[]>([]);
const search = async () => {
req.page = paginationConfig.currentPage;
req.pageSize = paginationConfig.pageSize;
loading.value = true;
try {
const res = await SearchRuntimes(req);
items.value = res.data.items;
paginationConfig.total = res.data.total;
} catch (error) {
} finally {
loading.value = false;
}
};
onMounted(() => {
search();
});
</script>