feat: 增加网站创建功能

This commit is contained in:
zhengkunwang223 2022-10-28 17:04:57 +08:00 committed by zhengkunwang223
parent a1ac689a5e
commit ef789cdfea
60 changed files with 1156 additions and 70 deletions

View File

@ -10,7 +10,7 @@
"name": "服务器"
},
{
"key": "Datastore",
"key": "Database",
"name": "数据库"
}
],
@ -18,7 +18,7 @@
{
"key": "mysql5.7",
"name": "Mysql5.7",
"tags": ["Datastore"],
"tags": ["Database"],
"versions": ["5.7.39"],
"short_desc": "常用关系型数据库",
"icon": "mysql.png",
@ -32,7 +32,7 @@
{
"key": "mysql8.0",
"name": "Mysql8.0",
"tags": ["Datastore"],
"tags": ["Database"],
"versions": ["8.0.30"],
"short_desc": "常用关系型数据库",
"icon": "mysql.png",
@ -66,7 +66,7 @@
"icon": "wordpress.png",
"author": "Wordpress",
"type": "website",
"required": ["mysql"],
"required": ["mysql8.0"],
"limit": 0,
"crossVersionUpdate": true,
"source": "http://wordpress.org/"
@ -74,7 +74,7 @@
{
"key": "redis",
"name": "redis",
"tags": ["Datastore"],
"tags": ["Database"],
"versions": ["7.0.5","6.0.16"],
"short_desc": "缓存数据库",
"icon": "redis.png",

View File

@ -0,0 +1,99 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@ -11,7 +11,7 @@ events {
http {
include /etc/nginx/mime.types;
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
@ -23,14 +23,4 @@ http {
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name 127.0.0.1;
allow 127.0.0.1;
location /nginx_status {
stub_status on;
access_log off;
}
}
}

View File

@ -10,10 +10,8 @@ services:
- ${PANEL_APP_PORT_HTTPS}:443
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./www:/home/www
- ./log:/var/log/nginx
- ./conf/conf.d:/etc/nginx/conf.d/
- ./html:/usr/share/nginx/html
networks:
1panel:

View File

@ -2,7 +2,7 @@
"formFields": [
{
"type": "service",
"key": "mysql",
"key": "mysql8.0",
"labelZh": "数据库服务",
"labelEn": "Database Service",
"required": true,

View File

@ -2,7 +2,7 @@
"formFields": [
{
"type": "service",
"key": "mysql",
"key": "mysql8.0",
"labelZh": "数据库服务",
"labelEn": "Database Service",
"required": true,

View File

@ -1,13 +1,13 @@
package v1
import (
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
"reflect"
"strconv"
)
func (b *BaseApi) SearchApp(c *gin.Context) {
@ -98,16 +98,26 @@ func (b *BaseApi) SearchInstalled(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := appService.PageInstalled(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if !reflect.DeepEqual(req.PageInfo, dto.PageInfo{}) {
total, list, err := appService.PageInstalled(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
} else {
list, err := appService.SearchInstalled(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
}
func (b *BaseApi) SearchInstalledBackup(c *gin.Context) {

View File

@ -25,11 +25,13 @@ var (
hostService = service.ServiceGroupApp.HostService
groupService = service.ServiceGroupApp.GroupService
commandService = service.ServiceGroupApp.CommandService
fileService = service.ServiceGroupApp.FileService
settingService = service.ServiceGroupApp.SettingService
backupService = service.ServiceGroupApp.BackupService
operationService = service.ServiceGroupApp.OperationService
commandService = service.ServiceGroupApp.CommandService
websiteGroupService = service.ServiceGroupApp.WebsiteGroupService
websiteService = service.ServiceGroupApp.WebsiteService
)

View File

@ -0,0 +1,24 @@
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/constant"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) CreateWebsite(c *gin.Context) {
var req dto.WebSiteCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
err := websiteService.CreateWebsite(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -0,0 +1,17 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) GetGroups(c *gin.Context) {
list, err := websiteGroupService.GetGroups()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}

View File

@ -48,6 +48,7 @@ type AppInstalled struct {
type AppInstalledRequest struct {
PageInfo
Type string `json:"type"`
}
type AppBackupRequest struct {

View File

@ -0,0 +1,16 @@
package dto
type WebPage struct {
PageInfo
}
type WebSiteCreate struct {
PrimaryDomain string `json:"primaryDomain"`
Type string `json:"type"`
Alias string `json:"alias"`
Remark string `json:"remark"`
Domains []string `json:"domains"`
AppType string `json:"appType"`
AppInstallID uint `json:"appInstallID"`
WebSiteGroupID uint `json:"webSiteGroupID"`
}

View File

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

View File

@ -0,0 +1,16 @@
package model
import "time"
type WebSite struct {
BaseModel
PrimaryDomain string `gorm:"type:varchar(128);not null" json:"primaryDomain"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Alias string `gorm:"type:varchar(128);not null" json:"alias"`
Remark string `gorm:"type:longtext;" json:"remark"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
ExpireDate time.Time `json:"expireDate"`
AppInstallID uint `gorm:"type:integer" json:"appInstallID"`
WebSiteGroupID uint `gorm:"type:integer" json:"webSiteGroupID"`
WebSiteSSLID uint `gorm:"type:integer" json:"webSiteSSLID"`
}

View File

@ -0,0 +1,8 @@
package model
type WebSiteDomain struct {
BaseModel
WebSiteID uint `gorm:"type:varchar(64);not null" json:"web_site_id"`
Domain string `gorm:"type:varchar(256);not null" json:"domain"`
Port int `gorm:"type:integer" json:"port"`
}

View File

@ -0,0 +1,7 @@
package model
type WebSiteGroup struct {
BaseModel
Name string `gorm:"type:varchar(64);not null" json:"name"`
Default bool `json:"default"`
}

View File

@ -0,0 +1,5 @@
package model
type WebSiteSSL struct {
BaseModel
}

View File

@ -16,6 +16,12 @@ func (a AppRepo) WithKey(key string) DBOption {
}
}
func (a AppRepo) WithType(typeStr string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("type = ?", typeStr)
}
}
func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) {
var apps []model.App
db := getDb(opts...).Model(&model.App{})

View File

@ -25,6 +25,13 @@ func (a AppInstallRepo) WithAppId(appId uint) DBOption {
return g.Where("app_id = ?", appId)
}
}
func (a AppInstallRepo) WithAppIdsIn(appIds []uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("app_id in (?)", appIds)
}
}
func (a AppInstallRepo) WithStatus(status string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status = ?", status)

View File

@ -3,7 +3,6 @@ package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
@ -17,24 +16,16 @@ func (d DatabaseRepo) ByAppInstallId(installId uint) DBOption {
}
func (d DatabaseRepo) Create(ctx context.Context, database *model.AppDatabase) error {
db := ctx.Value("db").(*gorm.DB).Model(&model.AppDatabase{})
return db.Create(&database).Error
return getTx(ctx).Model(&model.AppDatabase{}).Create(&database).Error
}
func (d DatabaseRepo) DeleteBy(ctx context.Context, opts ...DBOption) error {
db := ctx.Value("db").(*gorm.DB).Model(&model.AppDatabase{})
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.AppDatabase{}).Error
return getTx(ctx, opts...).Model(&model.AppDatabase{}).Delete(&model.AppDatabase{}).Error
}
func (d DatabaseRepo) GetBy(opts ...DBOption) ([]model.AppDatabase, error) {
db := global.DB.Model(model.AppDatabase{})
db := getDb(opts...)
var databases []model.AppDatabase
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&databases).Error
if err != nil {
return nil, err
@ -43,11 +34,8 @@ func (d DatabaseRepo) GetBy(opts ...DBOption) ([]model.AppDatabase, error) {
}
func (d DatabaseRepo) GetFirst(opts ...DBOption) (model.AppDatabase, error) {
db := global.DB.Model(model.AppDatabase{})
db := getDb(opts...)
var database model.AppDatabase
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&database).Error
if err != nil {
return database, err

View File

@ -2,7 +2,6 @@ package repo
type RepoGroup struct {
CommonRepo
AppRepo
AppTagRepo
TagRepo
@ -11,22 +10,19 @@ type RepoGroup struct {
AppInstallResourceRpo
DatabaseRepo
AppInstallBackupRepo
ImageRepoRepo
ComposeTemplateRepo
MysqlRepo
CronjobRepo
HostRepo
CommandRepo
GroupRepo
SettingRepo
BackupRepo
OperationRepo
WebSiteRepo
WebSiteDomainRepo
WebSiteGroupRepo
}
var RepoGroupApp = new(RepoGroup)

View File

@ -0,0 +1,45 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm/clause"
)
type WebSiteRepo struct {
}
func (w WebSiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSite, error) {
var websites []model.WebSite
db := getDb(opts...).Model(&model.WebSite{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Find(&websites).Error
return count, websites, err
}
func (w WebSiteRepo) GetFirst(opts ...DBOption) (model.WebSite, error) {
var website model.WebSite
db := getDb(opts...).Model(&model.WebSite{})
if err := db.First(&website).Error; err != nil {
return website, err
}
return website, nil
}
func (w WebSiteRepo) GetBy(opts ...DBOption) ([]model.WebSite, error) {
var websites []model.WebSite
db := getDb(opts...).Model(&model.WebSite{})
if err := db.Find(&websites).Error; err != nil {
return websites, err
}
return websites, nil
}
func (w WebSiteRepo) Create(ctx context.Context, app *model.WebSite) error {
return getTx(ctx).Omit(clause.Associations).Create(app).Error
}
func (w WebSiteRepo) Save(ctx context.Context, app *model.WebSite) error {
return getTx(ctx).Omit(clause.Associations).Save(app).Error
}

View File

@ -0,0 +1,49 @@
package repo
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm/clause"
)
type WebSiteDomainRepo struct {
}
func (w WebSiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSiteDomain, error) {
var domains []model.WebSiteDomain
db := getDb(opts...).Model(&model.WebSiteDomain{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Find(&domains).Error
return count, domains, err
}
func (w WebSiteDomainRepo) GetFirst(opts ...DBOption) (model.WebSiteDomain, error) {
var domain model.WebSiteDomain
db := getDb(opts...).Model(&model.WebSiteDomain{})
if err := db.First(&domain).Error; err != nil {
return domain, err
}
return domain, nil
}
func (w WebSiteDomainRepo) GetBy(opts ...DBOption) ([]model.WebSiteDomain, error) {
var domains []model.WebSiteDomain
db := getDb(opts...).Model(&model.WebSiteDomain{})
if err := db.Find(&domains).Error; err != nil {
return domains, err
}
return domains, nil
}
func (w WebSiteDomainRepo) BatchCreate(ctx context.Context, domains []model.WebSiteDomain) error {
return getTx(ctx).Model(&model.WebSiteDomain{}).Create(&domains).Error
}
func (w WebSiteDomainRepo) Create(ctx context.Context, app *model.WebSiteDomain) error {
return getTx(ctx).Omit(clause.Associations).Create(app).Error
}
func (w WebSiteDomainRepo) Save(ctx context.Context, app *model.WebSiteDomain) error {
return getTx(ctx).Omit(clause.Associations).Save(app).Error
}

View File

@ -0,0 +1,39 @@
package repo
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm/clause"
)
type WebSiteGroupRepo struct {
}
func (w WebSiteGroupRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSiteGroup, error) {
var groups []model.WebSiteGroup
db := getDb(opts...).Model(&model.WebSiteGroup{})
count := int64(0)
db = db.Count(&count)
err := db.Debug().Limit(size).Offset(size * (page - 1)).Find(&groups).Error
return count, groups, err
}
func (w WebSiteGroupRepo) GetBy(opts ...DBOption) ([]model.WebSiteGroup, error) {
var groups []model.WebSiteGroup
db := getDb(opts...).Model(&model.WebSiteGroup{})
if err := db.Find(&groups).Error; err != nil {
return groups, err
}
return groups, nil
}
func (w WebSiteGroupRepo) Create(app *model.WebSiteGroup) error {
return getDb().Omit(clause.Associations).Create(app).Error
}
func (w WebSiteGroupRepo) Save(app *model.WebSiteGroup) error {
return getDb().Omit(clause.Associations).Save(app).Error
}
func (w WebSiteGroupRepo) DeleteBy(opts ...DBOption) error {
return getDb(opts...).Delete(&model.WebSiteGroup{}).Error
}

View File

@ -127,6 +127,40 @@ func (a AppService) PageInstalled(req dto.AppInstalledRequest) (int64, []dto.App
return total, installDTOs, nil
}
func (a AppService) SearchInstalled(req dto.AppInstalledRequest) ([]dto.AppInstalled, error) {
var installs []model.AppInstall
var err error
if req.Type != "" {
apps, err := appRepo.GetBy(appRepo.WithType(req.Type))
if err != nil {
return nil, err
}
var ids []uint
for _, app := range apps {
ids = append(ids, app.ID)
}
installs, err = appInstallRepo.GetBy(appInstallRepo.WithAppIdsIn(ids))
if err != nil {
return nil, err
}
} else {
installs, err = appInstallRepo.GetBy()
if err != nil {
return nil, err
}
}
var installDTOs []dto.AppInstalled
for _, in := range installs {
installDto := dto.AppInstalled{
AppInstall: in,
}
installDTOs = append(installDTOs, installDto)
}
return installDTOs, nil
}
func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO, error) {
var (

View File

@ -26,6 +26,8 @@ type ServiceGroup struct {
BackupService
OperationService
WebsiteGroupService
WebsiteService
}
var ServiceGroupApp = new(ServiceGroup)
@ -57,4 +59,7 @@ var (
backupRepo = repo.RepoGroupApp.BackupRepo
operationRepo = repo.RepoGroupApp.OperationRepo
websiteRepo = repo.RepoGroupApp.WebSiteRepo
websiteGroupRepo = repo.RepoGroupApp.WebSiteGroupRepo
websiteDomainRepo = repo.RepoGroupApp.WebSiteDomainRepo
)

View File

@ -2,16 +2,18 @@ package service
import (
"context"
"github.com/1Panel-dev/1Panel/backend/global"
"gorm.io/gorm"
)
type dbStr string
type DBContext string
const (
DB DBContext = "db"
)
func getTxAndContext() (tx *gorm.DB, ctx context.Context) {
db := dbStr("db")
tx = global.DB.Begin()
ctx = context.WithValue(context.Background(), db, tx)
ctx = context.WithValue(context.Background(), DB, tx)
return
}

View File

@ -0,0 +1,60 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"reflect"
"time"
)
type WebsiteService struct {
}
func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate)
website := &model.WebSite{
PrimaryDomain: create.PrimaryDomain,
Type: create.Type,
Alias: create.Alias,
Remark: create.Remark,
Status: constant.WebRunning,
ExpireDate: defaultDate,
AppInstallID: create.AppInstallID,
WebSiteGroupID: create.WebSiteGroupID,
}
tx, ctx := getTxAndContext()
if err := websiteRepo.Create(ctx, website); err != nil {
return err
}
var domains []model.WebSiteDomain
domains = append(domains, model.WebSiteDomain{Domain: website.PrimaryDomain, WebSiteID: website.ID, Port: 80})
for _, domain := range create.Domains {
domainModel, err := getDomain(domain, website.ID)
if err != nil {
tx.Rollback()
return err
}
if reflect.DeepEqual(domainModel, model.WebSiteDomain{}) {
continue
}
domains = append(domains, domainModel)
}
if len(domains) > 0 {
if err := websiteDomainRepo.BatchCreate(ctx, domains); err != nil {
tx.Rollback()
return err
}
}
if err := configDefaultNginx(*website, domains); err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}

View File

@ -0,0 +1,32 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type WebsiteGroupService struct {
}
func (w WebsiteGroupService) CreateGroup(create dto.WebSiteGroupCreateReq) error {
return websiteGroupRepo.Create(&model.WebSiteGroup{
Name: create.Name,
})
}
func (w WebsiteGroupService) GetGroups() ([]model.WebSiteGroup, error) {
return websiteGroupRepo.GetBy()
}
func (w WebsiteGroupService) UpdateGroup(update dto.WebSiteGroupUpdateReq) error {
return websiteGroupRepo.Save(&model.WebSiteGroup{
BaseModel: model.BaseModel{
ID: update.ID,
},
Name: update.Name,
})
}
func (w WebsiteGroupService) DeleteGroup(id uint) error {
return websiteGroupRepo.DeleteBy(commonRepo.WithByID(id))
}

View File

@ -0,0 +1,92 @@
package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
"github.com/pkg/errors"
"path"
"strconv"
"strings"
)
func getDomain(domainStr string, websiteID uint) (model.WebSiteDomain, error) {
domain := model.WebSiteDomain{
WebSiteID: websiteID,
}
domainArray := strings.Split(domainStr, ":")
if len(domainArray) == 1 {
domain.Domain = domainArray[0]
return domain, nil
}
if len(domainArray) > 1 {
domain.Domain = domainArray[0]
portStr := domainArray[1]
portN, err := strconv.Atoi(portStr)
if err != nil {
return model.WebSiteDomain{}, err
}
domain.Port = portN
return domain, nil
}
return model.WebSiteDomain{}, nil
}
func configDefaultNginx(website model.WebSite, domains []model.WebSiteDomain) 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
}
appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
nginxFileName := website.PrimaryDomain + ".conf"
configPath := path.Join(constant.AppInstallDir, "nginx", nginxInstall.Name, "conf", "conf.d", nginxFileName)
nginxContent := string(nginx_conf.WebsiteDefault)
config := parser.NewStringParser(nginxContent).Parse()
servers := config.FindServers()
if len(servers) == 0 {
return errors.New("nginx config is not valid")
}
server := servers[0]
var serverNames []string
for _, domain := range domains {
serverNames = append(serverNames, domain.Domain)
server.UpdateListen(string(rune(domain.Port)), false)
}
server.UpdateServerName(serverNames)
proxy := fmt.Sprintf("%s:%d", appInstall.ServiceName, appInstall.HttpPort)
server.UpdateRootProxy([]string{proxy})
config.FilePath = configPath
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
}
if err := opNginx(nginxInstall.ContainerName, "check"); err != nil {
return err
}
return opNginx(nginxInstall.ContainerName, "reload")
}
func opNginx(containerName, operate string) error {
nginxCmd := fmt.Sprintf("docker exec -i %s %s", containerName, "nginx -s reload")
if operate == "check" {
nginxCmd = fmt.Sprintf("docker exec -i %s %s", containerName, "nginx -t")
}
if _, err := cmd.Exec(nginxCmd); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,11 @@
package constant
const (
WebRunning = "Running"
WebStopped = "Stopped"
)
const (
DateLayout = "2006-01-02"
DefaultDate = "1970-01-01"
)

View File

@ -17,6 +17,7 @@ func Init() {
migrations.AddTableCronjob,
migrations.AddTableApp,
migrations.AddTableImageRepo,
migrations.AddTableWebsite,
migrations.AddTableDatabaseMysql,
})
if err := m.Migrate(); err != nil {

View File

@ -177,3 +177,20 @@ var AddTableDatabaseMysql = &gormigrate.Migration{
return tx.AutoMigrate(&model.DatabaseMysql{})
},
}
var AddTableWebsite = &gormigrate.Migration{
ID: "20201009-add-table-website",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.WebSite{}, &model.WebSiteDomain{}, &model.WebSiteGroup{}); err != nil {
return err
}
item := &model.WebSiteGroup{
Name: "默认分组",
Default: true,
}
if err := tx.Create(item).Error; err != nil {
return err
}
return nil
},
}

View File

@ -79,6 +79,8 @@ func Routers() *gin.Engine {
systemRouter.InitCronjobRouter(PrivateGroup)
systemRouter.InitSettingRouter(PrivateGroup)
systemRouter.InitAppRouter(PrivateGroup)
systemRouter.InitWebsiteRouter(PrivateGroup)
systemRouter.InitWebsiteGroupRouter(PrivateGroup)
systemRouter.InitDatabaseRouter(PrivateGroup)
}

View File

@ -14,6 +14,8 @@ type RouterGroup struct {
CronjobRouter
SettingRouter
AppRouter
WebsiteRouter
WebsiteGroupRouter
DatabaseRouter
}

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 WebsiteRouter struct {
}
func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("websites")
groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("", baseApi.CreateWebsite)
}
}

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 WebsiteGroupRouter struct {
}
func (a *WebsiteGroupRouter) InitWebsiteGroupRouter(Router *gin.RouterGroup) {
groupRouter := Router.Group("websites/groups")
groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.GET("", baseApi.GetGroups)
}
}

View File

@ -48,3 +48,19 @@ func (b *Block) AddDirectives(directive Directive) {
directives := append(b.GetDirectives(), &directive)
b.Directives = directives
}
func (b *Block) RemoveDirectives(names []string) {
nameMaps := make(map[string]struct{}, len(names))
for _, name := range names {
nameMaps[name] = struct{}{}
}
directives := b.GetDirectives()
var newDirectives []IDirective
for _, dir := range directives {
if _, ok := nameMaps[dir.GetName()]; ok {
continue
}
newDirectives = append(newDirectives, dir)
}
b.Directives = newDirectives
}

View File

@ -88,6 +88,22 @@ func (h *Http) AddDirectives(directive Directive) {
h.Directives = directives
}
func (h *Http) RemoveDirectives(names []string) {
nameMaps := make(map[string]struct{}, len(names))
for _, name := range names {
nameMaps[name] = struct{}{}
}
directives := h.GetDirectives()
var newDirectives []IDirective
for _, dir := range directives {
if _, ok := nameMaps[dir.GetName()]; ok {
continue
}
newDirectives = append(newDirectives, dir)
}
h.Directives = newDirectives
}
func (h *Http) GetBlock() IBlock {
return h
}

View File

@ -16,9 +16,11 @@ func NewServer(directive IDirective) (*Server, error) {
server.Comment = block.GetComment()
directives := block.GetDirectives()
for _, dir := range directives {
if dir.GetName() == "listen" {
switch dir.GetName() {
case "listen":
server.Listens = append(server.Listens, NewServerListen(dir.GetParameters()))
} else {
default:
server.Directives = append(server.Directives, dir)
}
}
@ -63,6 +65,56 @@ func (s *Server) AddListen(bind string, defaultServer bool, params ...string) {
s.Listens = append(s.Listens, listen)
}
func (s *Server) UpdateListen(bind string, defaultServer bool, params ...string) {
listen := &ServerListen{
Bind: bind,
Parameters: params,
}
if defaultServer {
listen.DefaultServer = DefaultServer
}
var newListens []*ServerListen
for _, li := range s.Listens {
if li.Bind == bind {
newListens = append(newListens, listen)
} else {
newListens = append(newListens, li)
}
}
s.Listens = newListens
}
func (s *Server) UpdateServerName(names []string) {
serverNameDirective := Directive{
Name: "server_name",
Parameters: names,
}
s.UpdateDirectives("server_name", serverNameDirective)
}
func (s *Server) UpdateRootProxy(proxy []string) {
//TODD 根据ID修改
dirs := s.FindDirectives("location")
for _, dir := range dirs {
location, ok := dir.(*Location)
if ok && location.Match == "/" {
newDir := Directive{
Name: "location",
Parameters: []string{"/"},
Block: &Block{},
}
block := &Block{}
block.Directives = append(block.Directives, &Directive{
Name: "proxy_pass",
Parameters: proxy,
})
newDir.Block = block
s.UpdateDirectives("location", newDir)
}
}
}
func (s *Server) RemoveListenByBind(bind string) {
index := 0
listens := s.Listens
@ -105,3 +157,19 @@ func (s *Server) AddDirectives(directive Directive) {
directives := append(s.Directives, &directive)
s.Directives = directives
}
func (s *Server) RemoveDirectives(names []string) {
nameMaps := make(map[string]struct{}, len(names))
for _, name := range names {
nameMaps[name] = struct{}{}
}
directives := s.GetDirectives()
var newDirectives []IDirective
for _, dir := range directives {
if _, ok := nameMaps[dir.GetName()]; ok {
continue
}
newDirectives = append(newDirectives, dir)
}
s.Directives = newDirectives
}

View File

@ -5,6 +5,7 @@ type IBlock interface {
FindDirectives(directiveName string) []IDirective
UpdateDirectives(directiveName string, directive Directive)
AddDirectives(directive Directive)
RemoveDirectives(names []string)
GetComment() string
}

View File

@ -92,3 +92,19 @@ func (us *Upstream) AddDirectives(directive Directive) {
directives := append(us.GetDirectives(), &directive)
us.Directives = directives
}
func (us *Upstream) RemoveDirectives(names []string) {
nameMaps := make(map[string]struct{}, len(names))
for _, name := range names {
nameMaps[name] = struct{}{}
}
directives := us.GetDirectives()
var newDirectives []IDirective
for _, dir := range directives {
if _, ok := nameMaps[dir.GetName()]; ok {
continue
}
newDirectives = append(newDirectives, dir)
}
us.Directives = newDirectives
}

View File

@ -5,8 +5,6 @@ import (
"fmt"
components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
@ -123,10 +121,5 @@ func DumpConfig(c *components.Config, style *Style) string {
}
func WriteConfig(c *components.Config, style *Style) error {
dir, _ := filepath.Split(c.FilePath)
err := os.MkdirAll(dir, 0755)
if err != nil {
return err
}
return ioutil.WriteFile(c.FilePath, []byte(DumpConfig(c, style)), 0644)
}

View File

@ -0,0 +1,4 @@
gzip on;
gzip_comp_level 6;
gzip_min_length 1k;
gzip_types text/plain text/css text/xml text/javascript text/x-component application/json application/javascript application/x-javascript application/xml application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype;

View File

@ -0,0 +1,3 @@
if ($server_port !~ 443){
rewrite ^(/.*)$ https://$host$1 permanent;
}

View File

@ -0,0 +1,3 @@
limit_conn perserver 300;
limit_conn perip 25;
limit_rate 512k;

View File

@ -0,0 +1,14 @@
package nginx_conf
import (
_ "embed"
)
//go:embed ssl.conf
var SSL []byte
//go:embed http2https.conf
var HTTPS []byte
//go:embed website_default.conf
var WebsiteDefault []byte

View File

@ -0,0 +1,9 @@
ssl_certificate /www/server/panel/vhost/cert/1panel.cloud/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/1panel.cloud/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;

View File

@ -0,0 +1,17 @@
server {
listen 80;
server_name ko.wp-1.com;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
location / {
proxy_pass http://127.0.0.1:8080;
}
access_log /var/log/nginx/nginx-log.log;
}

View File

@ -88,6 +88,10 @@ export namespace App {
detailId?: number;
}
export interface AppInstalledSearch {
type: string;
}
export interface AppService {
label: string;
value: string;

View File

@ -0,0 +1,19 @@
import { CommonModel } from '.';
export namespace WebSite {
export interface WebSiteCreateReq {
primaryDomain: string;
type: string;
alias: string;
remark: string;
domains: string[];
appType: string;
appInstallID: number;
webSiteGroupID: number;
otherDomains: string;
}
export interface Group extends CommonModel {
name: string;
}
}

View File

@ -30,6 +30,10 @@ export const GetAppInstalled = (info: ReqPage) => {
return http.post<ResPage<App.AppInstalled>>('apps/installed', info);
};
export const SearchAppInstalled = (search: App.AppInstalledSearch) => {
return http.post<App.AppInstalled[]>('apps/installed', search);
};
export const InstalledOp = (op: App.AppInstalledOp) => {
return http.post<any>('apps/installed/op', op);
};

View File

@ -0,0 +1,10 @@
import http from '@/api';
import { WebSite } from '../interface/website';
export const listGroups = () => {
return http.get<WebSite.Group[]>(`/websites/groups`);
};
export const CreateWebsite = (req: WebSite.WebSiteCreateReq) => {
return http.post<any>(`/websites`, req);
};

View File

@ -662,4 +662,19 @@ export default {
update: '更新',
versioneSelect: '请选择版本',
},
website: {
primaryDomain: '主域名',
otherDomains: '其他域名',
type: '类型',
static: '静态网站',
deployment: '一键部署',
proxy: '反向代理',
alias: '代号',
remark: '备注',
group: '分组',
app: '应用',
app_new: '新装应用',
app_installed: '已装应用',
create: '创建网站',
},
};

View File

@ -18,6 +18,15 @@ const webSiteRouter = {
title: 'menu.project',
},
},
{
path: '/websites/projects/config',
name: 'WebsiteConfig',
component: () => import('@/views/website/project/config/index.vue'),
hidden: true,
meta: {
activeMenu: '/websites',
},
},
{
path: '/websites/config',
name: 'Config',

View File

@ -9,7 +9,7 @@ export const GlobalStore = defineStore({
state: (): GlobalState => ({
isLogin: false,
csrfToken: '',
assemblySize: 'small',
assemblySize: 'default',
language: '',
themeConfig: {
panelName: '',

View File

@ -0,0 +1,37 @@
<template>
<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>
</el-tab-pane>
<el-tab-pane label="优化">优化</el-tab-pane>
<el-tab-pane label="反代">反代</el-tab-pane>
<el-tab-pane label="安全">反代</el-tab-pane>
<el-tab-pane label="备份">反代</el-tab-pane>
<el-tab-pane label="日志">反代</el-tab-pane>
<el-tab-pane label="源文">反代</el-tab-pane>
</el-tabs>
</LayoutContent>
</template>
<script setup lang="ts">
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
</script>

View File

@ -0,0 +1,135 @@
<template>
<el-dialog v-model="open" :title="$t('website.create')" width="40%" :before-close="handleClose">
<el-form ref="websiteForm" label-position="right" :model="website" label-width="100px" :rules="rules">
<el-form-item :label="$t('website.type')" prop="type">
<el-select v-model="website.type">
<el-option :label="$t('website.deployment')" value="deployment"></el-option>
<el-option :label="$t('website.static')" value="static"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.group')" prop="webSiteGroupID">
<el-select v-model="website.webSiteGroupID">
<el-option
v-for="(group, index) in groups"
:key="index"
:label="group.name"
:value="group.id"
></el-option>
</el-select>
</el-form-item>
<div v-if="website.type === 'deployment'">
<el-form-item prop="appType">
<el-radio-group v-model="website.appType">
<el-radio :label="'installed'" :value="'installed'">
{{ $t('website.app_installed') }}
</el-radio>
<el-radio :label="'new'">
{{ $t('website.app_new') }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="website.appType == 'installed'"
:label="$t('website.app_installed')"
prop="appInstallID"
>
<el-select v-model="website.appInstallID">
<el-option
v-for="(appInstall, index) in appInstalles"
:key="index"
:label="appInstall.name"
:value="appInstall.id"
></el-option>
</el-select>
</el-form-item>
</div>
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
<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-form-item>
<el-form-item :label="$t('website.alias')" prop="alias">
<el-input v-model="website.alias"></el-input>
</el-form-item>
<el-form-item :label="$t('website.remark')" prop="remark">
<el-input v-model="website.remark"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(websiteForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup name="CreateWebSite">
import { App } from '@/api/interface/app';
import { WebSite } from '@/api/interface/website';
import { SearchAppInstalled } from '@/api/modules/app';
import { CreateWebsite, listGroups } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import { FormRules, ElDialog, ElForm, FormInstance } from 'element-plus';
import { reactive, ref } from 'vue';
const websiteForm = ref<FormInstance>();
const website = reactive({
primaryDomain: '',
type: 'deployment',
alias: '',
remark: '',
domains: [],
appType: 'installed',
appInstallID: 0,
webSiteGroupID: 1,
otherDomains: '',
});
let rules = reactive<FormRules>({
primaryDomain: [Rules.requiredInput],
alias: [Rules.requiredInput],
type: [Rules.requiredInput],
webSiteGroupID: [Rules.requiredInput],
appInstallID: [Rules.requiredInput],
appType: [Rules.requiredInput],
});
let open = ref(false);
let loading = ref(false);
let groups = ref<WebSite.Group[]>([]);
let appInstalles = ref<App.AppInstalled[]>([]);
const handleClose = () => {
open.value = false;
};
const acceptParams = async () => {
await listGroups().then((res) => {
groups.value = res.data;
open.value = true;
});
await SearchAppInstalled({ type: 'website' }).then((res) => {
appInstalles.value = res.data;
if (res.data.length > 0) {
website.appInstallID = res.data[0].id;
}
});
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
CreateWebsite(website).then(() => {});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,7 +1,69 @@
<template>
<LayoutContent></LayoutContent>
<LayoutContent :header="'网站'">
<ComplexTable :data="data" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">{{ '新建网站' }}</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.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"></CreateWebSite>
</LayoutContent>
</template>
<script lang="ts" setup>
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { onMounted, ref } from '@vue/runtime-core';
import router from '@/routers';
import CreateWebSite from './create/index.vue';
const createRef = ref();
const data = ref();
const search = async () => {
data.value = [
{
primaryDomain: 'www.baicu.com',
status: 'Running',
backup: '1',
remark: '主网站',
},
];
};
const openConfig = () => {
router.push({ name: 'WebsiteConfig' });
};
const buttons = [
{
label: '设置',
click: open,
},
];
const openCreate = () => {
createRef.value.acceptParams();
};
onMounted(() => {
search();
});
</script>