feat: 增加网站域名管理

This commit is contained in:
zhengkunwang223 2022-11-03 17:06:48 +08:00 committed by zhengkunwang223
parent 42134e315b
commit 35e85930b9
23 changed files with 360 additions and 73 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
"reflect"
"strconv"
)
func (b *BaseApi) SearchApp(c *gin.Context) {
@ -35,13 +34,13 @@ func (b *BaseApi) SyncApp(c *gin.Context) {
}
func (b *BaseApi) GetApp(c *gin.Context) {
idStr := c.Param("id")
u64, err := strconv.ParseUint(idStr, 10, 32)
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
appDTO, err := appService.GetApp(uint(u64))
appDTO, err := appService.GetApp(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@ -49,14 +48,15 @@ func (b *BaseApi) GetApp(c *gin.Context) {
helper.SuccessWithData(c, appDTO)
}
func (b *BaseApi) GetAppDetail(c *gin.Context) {
idStr := c.Param("appid")
u64, err := strconv.ParseUint(idStr, 10, 32)
appId, err := helper.GetIntParamByKey(c, "appId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
version := c.Param("version")
appDetailDTO, err := appService.GetAppDetail(uint(u64), version)
appDetailDTO, err := appService.GetAppDetail(appId, version)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
@ -175,17 +175,13 @@ func (b *BaseApi) GetServices(c *gin.Context) {
func (b *BaseApi) GetUpdateVersions(c *gin.Context) {
appInstallId := c.Param("appInstallId")
if appInstallId == "" {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, nil)
return
}
id, err := strconv.Atoi(appInstallId)
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
versions, err := appService.GetUpdateVersions(uint(id))
versions, err := appService.GetUpdateVersions(appInstallId)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View File

@ -1,6 +1,7 @@
package helper
import (
"fmt"
"net/http"
"strconv"
@ -85,3 +86,12 @@ func GetParamID(c *gin.Context) (uint, error) {
intNum, _ := strconv.Atoi(idParam)
return uint(intNum), nil
}
func GetIntParamByKey(c *gin.Context, key string) (uint, error) {
idParam, ok := c.Params.Get(key)
if !ok {
return 0, fmt.Errorf("error %s in path", key)
}
intNum, _ := strconv.Atoi(idParam)
return uint(intNum), nil
}

View File

@ -51,3 +51,34 @@ func (b *BaseApi) DeleteWebSite(c *gin.Context) {
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) GetWebDomains(c *gin.Context) {
websiteId, err := helper.GetIntParamByKey(c, "websiteId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
list, err := websiteService.GetWebsiteDomain(websiteId)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
func (b *BaseApi) DeleteWebDomain(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
if err := websiteService.DeleteWebsiteDomain(id); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -5,7 +5,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
"strconv"
)
func (b *BaseApi) GetWebGroups(c *gin.Context) {
@ -45,17 +44,13 @@ func (b *BaseApi) UpdateWebGroup(c *gin.Context) {
func (b *BaseApi) DeleteWebGroup(c *gin.Context) {
groupId := c.Param("groupId")
if groupId == "" {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, nil)
return
}
id, err := strconv.Atoi(groupId)
groupId, err := helper.GetIntParamByKey(c, "groupId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
if err := websiteGroupService.DeleteGroup(uint(id)); err != nil {
if err := websiteGroupService.DeleteGroup(groupId); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View File

@ -18,7 +18,7 @@ type WebSiteCreate struct {
Type string `json:"type" validate:"required"`
Alias string `json:"alias" validate:"required"`
Remark string `json:"remark"`
Domains []string `json:"domains"`
OtherDomains string `json:"otherDomains"`
AppType AppType `json:"appType" validate:"required"`
AppInstall NewAppInstall `json:"appInstall"`
AppID uint `json:"appID"`
@ -41,3 +41,18 @@ type WebSiteDel struct {
type WebSiteDTO struct {
model.WebSite
}
type WebSiteGroupCreate struct {
Name string
}
type WebSiteGroupUpdate struct {
ID uint
Name string
}
type WebSiteDomainCreate struct {
}
type WebSiteDomainDel struct {
}

View File

@ -1,10 +0,0 @@
package dto
type WebSiteGroupCreate struct {
Name string
}
type WebSiteGroupUpdate struct {
ID uint
Name string
}

View File

@ -71,12 +71,13 @@ func (c *CommonRepo) WithIdsNotIn(ids []uint) DBOption {
}
func getTx(ctx context.Context, opts ...DBOption) *gorm.DB {
if ctx == nil || ctx.Value(constant.DB) == nil {
return getDb()
}
tx := ctx.Value(constant.DB).(*gorm.DB)
for _, opt := range opts {
tx = opt(tx)
tx, ok := ctx.Value(constant.DB).(*gorm.DB)
if ok {
for _, opt := range opts {
tx = opt(tx)
}
} else {
return getDb(opts...)
}
return tx
}

View File

@ -3,12 +3,19 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type WebSiteRepo struct {
}
func (w WebSiteRepo) WithAppInstallId(appInstallId uint) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("app_install_id = ?", appInstallId)
}
}
func (w WebSiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSite, error) {
var websites []model.WebSite
db := getDb(opts...).Model(&model.WebSite{})

View File

@ -15,6 +15,17 @@ func (w WebSiteDomainRepo) WithWebSiteId(websiteId uint) DBOption {
return db.Where("web_site_id = ?", websiteId)
}
}
func (w WebSiteDomainRepo) WithPort(port int) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("port = ?", port)
}
}
func (w WebSiteDomainRepo) WithDomain(domain string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("domain = ?", domain)
}
}
func (w WebSiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSiteDomain, error) {
var domains []model.WebSiteDomain
db := getDb(opts...).Model(&model.WebSiteDomain{})

View File

@ -1,12 +1,15 @@
package service
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/pkg/errors"
"gorm.io/gorm"
"reflect"
"strings"
"time"
)
@ -28,6 +31,7 @@ func (w WebsiteService) PageWebSite(req dto.WebSiteReq) (int64, []dto.WebSiteDTO
}
func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
website := &model.WebSite{
PrimaryDomain: create.PrimaryDomain,
@ -54,7 +58,9 @@ func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
}
var domains []model.WebSiteDomain
domains = append(domains, model.WebSiteDomain{Domain: website.PrimaryDomain, WebSiteID: website.ID, Port: 80})
for _, domain := range create.Domains {
otherDomainArray := strings.Split(create.OtherDomains, "\n")
for _, domain := range otherDomainArray {
domainModel, err := getDomain(domain, website.ID)
if err != nil {
tx.Rollback()
@ -93,6 +99,10 @@ func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error {
tx, ctx := getTxAndContext()
if req.DeleteApp {
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(website.AppInstallID))
if len(websites) > 1 {
return errors.New("other website use this app")
}
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
@ -115,3 +125,43 @@ func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error {
tx.Commit()
return nil
}
func (w WebsiteService) CreateWebsiteDomain() {
}
func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebSiteDomain, error) {
return websiteDomainRepo.GetBy(websiteDomainRepo.WithWebSiteId(websiteId))
}
func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
webSiteDomain, err := websiteDomainRepo.GetFirst(commonRepo.WithByID(domainId))
if err != nil {
return err
}
if websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebSiteId(webSiteDomain.WebSiteID)); len(websiteDomains) == 1 {
return fmt.Errorf("can not delete last domain")
}
website, err := websiteRepo.GetFirst(commonRepo.WithByID(webSiteDomain.WebSiteID))
if err != nil {
return err
}
var ports []int
if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebSiteId(webSiteDomain.WebSiteID), websiteDomainRepo.WithPort(webSiteDomain.Port)); len(oldDomains) == 1 {
ports = append(ports, webSiteDomain.Port)
}
var domains []string
if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebSiteId(webSiteDomain.WebSiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 {
domains = append(domains, webSiteDomain.Domain)
}
if len(ports) > 0 || len(domains) > 0 {
if err := deleteListenAndServerName(website, ports, domains); err != nil {
return err
}
}
return websiteDomainRepo.DeleteBy(context.TODO(), commonRepo.WithByID(domainId))
}

View File

@ -11,6 +11,7 @@ import (
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
"github.com/pkg/errors"
"gorm.io/gorm"
"os"
"path"
"strconv"
"strings"
@ -67,7 +68,7 @@ func configDefaultNginx(website *model.WebSite, domains []model.WebSiteDomain) e
var serverNames []string
for _, domain := range domains {
serverNames = append(serverNames, domain.Domain)
server.UpdateListen(string(rune(domain.Port)), false)
server.UpdateListen(strconv.Itoa(domain.Port), false)
}
server.UpdateServerName(serverNames)
proxy := fmt.Sprintf("http://%s:%d", appInstall.ServiceName, appInstall.HttpPort)
@ -121,6 +122,50 @@ func delNginxConfig(website model.WebSite) error {
return opNginx(nginxInstall.ContainerName, "reload")
}
func delApp() error {
func nginxCheckAndReload(oldContent string, filePath string, containerName string) error {
if err := opNginx(containerName, "check"); err != nil {
_ = files.NewFileOp().WriteFile(filePath, strings.NewReader(oldContent), 0644)
return err
}
if err := opNginx(containerName, "reload"); err != nil {
_ = files.NewFileOp().WriteFile(filePath, strings.NewReader(oldContent), 0644)
return err
}
return nil
}
func deleteListenAndServerName(website model.WebSite, ports []int, domains []string) error {
nginxApp, err := appRepo.GetFirst(appRepo.WithKey("nginx"))
if err != nil {
return err
}
nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID))
if err != nil {
return err
}
configPath := path.Join(constant.AppInstallDir, "nginx", nginxInstall.Name, "conf", "conf.d", website.PrimaryDomain+".conf")
content, err := os.ReadFile(configPath)
if err != nil {
return err
}
config := parser.NewStringParser(string(content)).Parse()
server := config.FindServers()[0]
for _, port := range ports {
server.DeleteListen(strconv.Itoa(port))
}
for _, domain := range domains {
server.DeleteServerName(domain)
}
config.FilePath = configPath
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
return nginxCheckAndReload(string(content), configPath, nginxInstall.ContainerName)
}

View File

@ -18,7 +18,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.POST("/sync", baseApi.SyncApp)
appRouter.POST("/search", baseApi.SearchApp)
appRouter.GET("/:id", baseApi.GetApp)
appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
appRouter.POST("/installed", baseApi.SearchInstalled)

View File

@ -18,5 +18,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
groupRouter.POST("", baseApi.CreateWebsite)
groupRouter.POST("/search", baseApi.PageWebsite)
groupRouter.POST("/del", baseApi.DeleteWebSite)
groupRouter.GET("/domains/:websiteId", baseApi.GetWebDomains)
groupRouter.DELETE("/domains/:id", baseApi.DeleteWebDomain)
}
}

View File

@ -74,17 +74,44 @@ func (s *Server) UpdateListen(bind string, defaultServer bool, params ...string)
listen.DefaultServer = DefaultServer
}
var newListens []*ServerListen
exist := false
for _, li := range s.Listens {
if li.Bind == bind {
exist = true
newListens = append(newListens, listen)
} else {
newListens = append(newListens, li)
}
}
if !exist {
newListens = append(newListens, listen)
}
s.Listens = newListens
}
func (s *Server) DeleteListen(bind string) {
var newListens []*ServerListen
for _, li := range s.Listens {
if li.Bind != bind {
newListens = append(newListens, li)
}
}
s.Listens = newListens
}
func (s *Server) DeleteServerName(name string) {
var names []string
dirs := s.FindDirectives("server_name")
params := dirs[0].GetParameters()
for _, param := range params {
if param != name {
names = append(names, param)
}
}
s.UpdateServerName(names)
}
func (s *Server) UpdateServerName(names []string) {
serverNameDirective := Directive{
Name: "server_name",

View File

@ -35,7 +35,6 @@ export namespace WebSite {
type: string;
alias: string;
remark: string;
domains: string[];
appType: string;
appInstallID: number;
webSiteGroupID: number;
@ -51,4 +50,10 @@ export namespace WebSite {
name: string;
id?: number;
}
export interface Domain {
name: string;
port: number;
id: number;
}
}

View File

@ -29,3 +29,11 @@ export const UpdateGroup = (req: WebSite.GroupOp) => {
export const DeleteGroup = (id: number) => {
return http.delete<any>(`/websites/groups/${id}`);
};
export const ListDomains = (id: number) => {
return http.get<WebSite.Domain[]>(`/websites/domains/${id}`);
};
export const DeleteDomain = (id: number) => {
return http.delete<any>(`/websites/domains/${id}`);
};

View File

@ -682,5 +682,8 @@ export default {
deleteApp: '删除应用',
deleteBackup: '删除备份',
domain: '域名',
domainHelper: '一行一个域名,支持*和IP地址,支持域名:端口',
port: '端口',
addDomain: '新增域名',
},
};

View File

@ -19,16 +19,17 @@ const webSiteRouter = {
},
},
{
path: '/websites/projects/config',
path: '/websites/:id/config',
name: 'WebsiteConfig',
component: () => import('@/views/website/project/config/index.vue'),
hidden: true,
props: true,
meta: {
activeMenu: '/websites',
},
},
{
path: '/websites/config',
path: '/websites/nginx',
name: 'Config',
component: () => import('@/views/website/config/index.vue'),
meta: {

View File

@ -0,0 +1,60 @@
<template>
<ComplexTable :data="data" @search="search" v-loading="loading">
<template #toolbar>
<el-button type="primary" plain>{{ $t('website.addDomain') }}</el-button>
</template>
<el-table-column :label="$t('website.domain')" prop="domain"></el-table-column>
<el-table-column :label="$t('website.port')" prop="port"></el-table-column>
<fu-table-operations :ellipsis="1" :buttons="buttons" :label="$t('commons.table.operate')" fixed="right" fix />
</ComplexTable>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { WebSite } from '@/api/interface/website';
import { DeleteDomain, ListDomains } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import i18n from '@/lang';
import { useDeleteData } from '@/hooks/use-delete-data';
const props = defineProps({
id: {
type: Number,
default: 0,
},
});
const id = computed(() => {
return props.id;
});
let loading = ref(false);
const data = ref<WebSite.Domain[]>([]);
const buttons = [
{
label: i18n.global.t('app.delete'),
click: function (row: WebSite.Domain) {
deleteDoamin(row.id);
},
},
];
const deleteDoamin = async (domainId: number) => {
await useDeleteData(DeleteDomain, domainId, 'commons.msg.delete', loading.value);
search(id.value);
};
const search = (id: number) => {
loading.value = true;
ListDomains(id)
.then((res) => {
data.value = res.data;
})
.finally(() => {
loading.value = false;
});
};
onMounted(() => search(id.value));
</script>

View File

@ -0,0 +1,31 @@
<template>
<el-tabs tab-position="left" type="border-card">
<el-tab-pane label="域名设置">
<Doamin :id="id"></Doamin>
</el-tab-pane>
<el-tab-pane label="目录设置">Config</el-tab-pane>
<el-tab-pane label="HTTPS">Role</el-tab-pane>
<el-tab-pane label="域名跳转">Task</el-tab-pane>
<el-tab-pane label="错误页面">Task</el-tab-pane>
<el-tab-pane label="过期时间">Task</el-tab-pane>
<el-tab-pane label="流量限制">Task</el-tab-pane>
<el-tab-pane label="默认文档">Task</el-tab-pane>
</el-tabs>
</template>
<script lang="ts" setup name="Basic">
import { computed } from 'vue';
import Doamin from './domain/index.vue';
const props = defineProps({
id: {
type: Number,
default: 0,
},
});
const id = computed(() => {
return props.id;
});
</script>

View File

@ -2,24 +2,7 @@
<LayoutContent :header="'网站设置'" :back-name="'Website'">
<el-tabs type="card">
<el-tab-pane label="基本">
<el-tabs tab-position="left" type="border-card">
<el-tab-pane label="域名设置">
<ComplexTable>
<template #toolbar>
<el-button type="primary" plain>{{ '新增域名' }}</el-button>
</template>
<el-table-column :label="'域名'" prop="backup"></el-table-column>
<el-table-column :label="'端口'" prop="remark"></el-table-column>
</ComplexTable>
</el-tab-pane>
<el-tab-pane label="目录设置">Config</el-tab-pane>
<el-tab-pane label="HTTPS">Role</el-tab-pane>
<el-tab-pane label="域名跳转">Task</el-tab-pane>
<el-tab-pane label="错误页面">Task</el-tab-pane>
<el-tab-pane label="过期时间">Task</el-tab-pane>
<el-tab-pane label="流量限制">Task</el-tab-pane>
<el-tab-pane label="默认文档">Task</el-tab-pane>
</el-tabs>
<Basic :id="id"></Basic>
</el-tab-pane>
<el-tab-pane label="优化">优化</el-tab-pane>
<el-tab-pane label="反代">反代</el-tab-pane>
@ -33,5 +16,17 @@
<script setup lang="ts">
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { computed } from 'vue';
import Basic from './basic/index.vue';
const props = defineProps({
id: {
type: Number,
default: 0,
},
});
const id = computed(() => {
return props.id;
});
</script>

View File

@ -86,7 +86,12 @@
<el-input v-model="website.primaryDomain"></el-input>
</el-form-item>
<el-form-item :label="$t('website.otherDomains')" prop="otherDomains">
<el-input v-model="website.otherDomains"></el-input>
<el-input
type="textarea"
:autosize="{ minRows: 2, maxRows: 6 }"
v-model="website.otherDomains"
:placeholder="$t('website.domainHelper')"
></el-input>
</el-form-item>
<el-form-item :label="$t('website.alias')" prop="alias">
<el-input v-model="website.alias"></el-input>
@ -123,7 +128,6 @@ const website = ref({
type: 'deployment',
alias: '',
remark: '',
domains: [],
appType: 'installed',
appInstallID: 0,
webSiteGroupID: 1,

View File

@ -9,7 +9,7 @@
</template>
<el-table-column :label="$t('commons.table.name')" fix show-overflow-tooltip prop="primaryDomain">
<template #default="{ row }">
<el-link @click="openConfig">{{ row.primaryDomain }}</el-link>
<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>
@ -66,8 +66,8 @@ const search = async () => {
});
};
const openConfig = () => {
router.push({ name: 'WebsiteConfig' });
const openConfig = (id: number) => {
router.push({ name: 'WebsiteConfig', params: { id: id } });
};
const buttons = [