mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-08 08:04:37 +08:00
feat: 菜单增加运行环境
This commit is contained in:
parent
8be00dad7f
commit
1949be2490
19 changed files with 318 additions and 1 deletions
34
backend/app/api/v1/runtime.go
Normal file
34
backend/app/api/v1/runtime.go
Normal 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,
|
||||
})
|
||||
}
|
||||
9
backend/app/dto/request/runtime.go
Normal file
9
backend/app/dto/request/runtime.go
Normal 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"`
|
||||
}
|
||||
7
backend/app/dto/response/runtime.go
Normal file
7
backend/app/dto/response/runtime.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package response
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
|
||||
type RuntimeRes struct {
|
||||
model.Runtime
|
||||
}
|
||||
13
backend/app/model/runtime.go
Normal file
13
backend/app/model/runtime.go
Normal 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"`
|
||||
}
|
||||
42
backend/app/repo/runtime.go
Normal file
42
backend/app/repo/runtime.go
Normal 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
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
42
backend/app/service/runtime.go
Normal file
42
backend/app/service/runtime.go
Normal 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
|
||||
}
|
||||
|
|
@ -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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ func Init() {
|
|||
migrations.AddTableDatabaseMysql,
|
||||
migrations.AddTableSnap,
|
||||
migrations.AddDefaultGroup,
|
||||
migrations.AddTableRuntime,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
|||
|
|
@ -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{})
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ func Routers() *gin.Engine {
|
|||
systemRouter.InitWebsiteSSLRouter(PrivateGroup)
|
||||
systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup)
|
||||
systemRouter.InitNginxRouter(PrivateGroup)
|
||||
systemRouter.InitRuntimeRouter(PrivateGroup)
|
||||
}
|
||||
|
||||
return Router
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type RouterGroup struct {
|
|||
WebsiteSSLRouter
|
||||
DatabaseRouter
|
||||
NginxRouter
|
||||
RuntimeRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
|
|
|
|||
20
backend/router/ro_runtime.go
Normal file
20
backend/router/ro_runtime.go
Normal 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)
|
||||
}
|
||||
}
|
||||
21
frontend/src/api/interface/runtime.ts
Normal file
21
frontend/src/api/interface/runtime.ts
Normal 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[];
|
||||
}
|
||||
}
|
||||
7
frontend/src/api/modules/runtime.ts
Normal file
7
frontend/src/api/modules/runtime.ts
Normal 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);
|
||||
};
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
73
frontend/src/views/website/runtime/index.vue
Normal file
73
frontend/src/views/website/runtime/index.vue
Normal 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>
|
||||
Loading…
Add table
Reference in a new issue