diff --git a/backend/app/api/v1/website.go b/backend/app/api/v1/website.go index 30797f875..caef8f261 100644 --- a/backend/app/api/v1/website.go +++ b/backend/app/api/v1/website.go @@ -778,3 +778,45 @@ func (b *BaseApi) GetDirConfig(c *gin.Context) { } helper.SuccessWithData(c, res) } + +// @Tags Website +// @Summary Get default html +// @Description 获取默认 html +// @Accept json +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Router /websites/default/html/:type [get] +func (b *BaseApi) GetDefaultHtml(c *gin.Context) { + resourceType, err := helper.GetStrParamByKey(c, "type") + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) + return + } + fileInfo, err := websiteService.GetDefaultHtml(resourceType) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, fileInfo) +} + +// @Tags Website +// @Summary Update default html +// @Description 更新默认 html +// @Accept json +// @Param request body request.WebsiteHtmlUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/default/html/update [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新默认 html","formatEN":"Update default html"} +func (b *BaseApi) UpdateDefaultHtml(c *gin.Context) { + var req request.WebsiteHtmlUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateDefaultHtml(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 2cd425e01..2082e1221 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -208,3 +208,12 @@ type WafWebsite struct { Domains []string `json:"domains"` Host []string `json:"host"` } + +type WebsiteHtmlReq struct { + Type string `json:"type" validate:"required"` +} + +type WebsiteHtmlUpdate struct { + Type string `json:"type" validate:"required"` + Content string `json:"content" validate:"required"` +} diff --git a/backend/app/dto/response/website.go b/backend/app/dto/response/website.go index 15cc78f85..8a31b69e8 100644 --- a/backend/app/dto/response/website.go +++ b/backend/app/dto/response/website.go @@ -82,3 +82,7 @@ type WebsiteDirConfig struct { UserGroup string `json:"userGroup"` Msg string `json:"msg"` } + +type WebsiteHtmlRes struct { + Content string `json:"content"` +} diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 45656a6b7..f94a20bd3 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -96,6 +96,9 @@ type IWebsiteService interface { OperateRedirect(req request.NginxRedirectReq) (err error) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) + + UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error + GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) } func NewIWebsiteService() IWebsiteService { @@ -2459,3 +2462,91 @@ func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*res return res, nil } + +func (w WebsiteService) GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + rootPath := path.Join(nginxInstall.GetPath(), "root") + fileOp := files.NewFileOp() + defaultPath := path.Join(rootPath, "default") + if !fileOp.Stat(defaultPath) { + _ = fileOp.CreateDir(defaultPath, 0755) + } + + res := &response.WebsiteHtmlRes{} + + switch resourceType { + case "404": + resourcePath := path.Join(defaultPath, "404.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.NotFoundHTML) + return res, nil + case "php": + resourcePath := path.Join(defaultPath, "index.php") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.IndexPHP) + return res, nil + case "index": + resourcePath := path.Join(defaultPath, "index.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.Index) + return res, nil + case "domain404": + resourcePath := path.Join(rootPath, "404.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.DomainNotFoundHTML) + return res, nil + case "stop": + resourcePath := path.Join(rootPath, "stop", "index.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.StopHTML) + return res, nil + } + return res, nil +} + +func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + rootPath := path.Join(nginxInstall.GetPath(), "root") + fileOp := files.NewFileOp() + defaultPath := path.Join(rootPath, "default") + if !fileOp.Stat(defaultPath) { + _ = fileOp.CreateDir(defaultPath, 0755) + } + var resourcePath string + switch req.Type { + case "404": + resourcePath = path.Join(defaultPath, "404.html") + case "php": + resourcePath = path.Join(defaultPath, "index.php") + case "index": + resourcePath = path.Join(defaultPath, "index.html") + case "domain404": + resourcePath = path.Join(rootPath, "404.html") + case "stop": + resourcePath = path.Join(rootPath, "stop", "index.html") + default: + return nil + } + return fileOp.SaveFile(resourcePath, req.Content, 0644) +} diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 179c7024b..38c4a59e8 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -317,7 +317,7 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro for _, domain := range domains { wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain) if domain.Port != 80 && domain.Port != 443 { - wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+string(rune(domain.Port))) + wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+strconv.Itoa(domain.Port)) } } websitesArray = append(websitesArray, wafWebsite) @@ -1092,3 +1092,14 @@ func checkSSLStatus(expireDate time.Time) string { } return "success" } + +func getResourceContent(fileOp files.FileOp, resourcePath string) (string, error) { + if fileOp.Stat(resourcePath) { + content, err := fileOp.GetContent(resourcePath) + if err != nil { + return "", err + } + return string(content), nil + } + return "", nil +} diff --git a/backend/router/ro_website.go b/backend/router/ro_website.go index c7100627c..7931c8003 100644 --- a/backend/router/ro_website.go +++ b/backend/router/ro_website.go @@ -10,59 +10,62 @@ type WebsiteRouter struct { } func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) { - groupRouter := Router.Group("websites") - groupRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired()) + websiteRouter := Router.Group("websites") + websiteRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired()) baseApi := v1.ApiGroupApp.BaseApi { - groupRouter.POST("/search", baseApi.PageWebsite) - groupRouter.GET("/list", baseApi.GetWebsites) - groupRouter.POST("", baseApi.CreateWebsite) - groupRouter.POST("/operate", baseApi.OpWebsite) - groupRouter.POST("/log", baseApi.OpWebsiteLog) - groupRouter.POST("/check", baseApi.CreateWebsiteCheck) - groupRouter.GET("/options", baseApi.GetWebsiteOptions) - groupRouter.POST("/update", baseApi.UpdateWebsite) - groupRouter.GET("/:id", baseApi.GetWebsite) - groupRouter.POST("/del", baseApi.DeleteWebsite) - groupRouter.POST("/default/server", baseApi.ChangeDefaultServer) + websiteRouter.POST("/search", baseApi.PageWebsite) + websiteRouter.GET("/list", baseApi.GetWebsites) + websiteRouter.POST("", baseApi.CreateWebsite) + websiteRouter.POST("/operate", baseApi.OpWebsite) + websiteRouter.POST("/log", baseApi.OpWebsiteLog) + websiteRouter.POST("/check", baseApi.CreateWebsiteCheck) + websiteRouter.GET("/options", baseApi.GetWebsiteOptions) + websiteRouter.POST("/update", baseApi.UpdateWebsite) + websiteRouter.GET("/:id", baseApi.GetWebsite) + websiteRouter.POST("/del", baseApi.DeleteWebsite) + websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer) - groupRouter.GET("/domains/:websiteId", baseApi.GetWebDomains) - groupRouter.POST("/domains/del", baseApi.DeleteWebDomain) - groupRouter.POST("/domains", baseApi.CreateWebDomain) + websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains) + websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain) + websiteRouter.POST("/domains", baseApi.CreateWebDomain) - groupRouter.GET("/:id/config/:type", baseApi.GetWebsiteNginx) - groupRouter.POST("/config", baseApi.GetNginxConfig) - groupRouter.POST("/config/update", baseApi.UpdateNginxConfig) - groupRouter.POST("/nginx/update", baseApi.UpdateWebsiteNginxConfig) + websiteRouter.GET("/:id/config/:type", baseApi.GetWebsiteNginx) + websiteRouter.POST("/config", baseApi.GetNginxConfig) + websiteRouter.POST("/config/update", baseApi.UpdateNginxConfig) + websiteRouter.POST("/nginx/update", baseApi.UpdateWebsiteNginxConfig) - groupRouter.GET("/:id/https", baseApi.GetHTTPSConfig) - groupRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig) + websiteRouter.GET("/:id/https", baseApi.GetHTTPSConfig) + websiteRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig) - groupRouter.GET("/php/config/:id", baseApi.GetWebsitePHPConfig) - groupRouter.POST("/php/config", baseApi.UpdateWebsitePHPConfig) - groupRouter.POST("/php/update", baseApi.UpdatePHPFile) - groupRouter.POST("/php/version", baseApi.ChangePHPVersion) + websiteRouter.GET("/php/config/:id", baseApi.GetWebsitePHPConfig) + websiteRouter.POST("/php/config", baseApi.UpdateWebsitePHPConfig) + websiteRouter.POST("/php/update", baseApi.UpdatePHPFile) + websiteRouter.POST("/php/version", baseApi.ChangePHPVersion) - groupRouter.POST("/rewrite", baseApi.GetRewriteConfig) - groupRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig) + websiteRouter.POST("/rewrite", baseApi.GetRewriteConfig) + websiteRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig) - groupRouter.POST("/dir/update", baseApi.UpdateSiteDir) - groupRouter.POST("/dir/permission", baseApi.UpdateSiteDirPermission) - groupRouter.POST("/dir", baseApi.GetDirConfig) + websiteRouter.POST("/dir/update", baseApi.UpdateSiteDir) + websiteRouter.POST("/dir/permission", baseApi.UpdateSiteDirPermission) + websiteRouter.POST("/dir", baseApi.GetDirConfig) - groupRouter.POST("/proxies", baseApi.GetProxyConfig) - groupRouter.POST("/proxies/update", baseApi.UpdateProxyConfig) - groupRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile) + websiteRouter.POST("/proxies", baseApi.GetProxyConfig) + websiteRouter.POST("/proxies/update", baseApi.UpdateProxyConfig) + websiteRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile) - groupRouter.POST("/auths", baseApi.GetAuthConfig) - groupRouter.POST("/auths/update", baseApi.UpdateAuthConfig) + websiteRouter.POST("/auths", baseApi.GetAuthConfig) + websiteRouter.POST("/auths/update", baseApi.UpdateAuthConfig) - groupRouter.POST("/leech", baseApi.GetAntiLeech) - groupRouter.POST("/leech/update", baseApi.UpdateAntiLeech) + websiteRouter.POST("/leech", baseApi.GetAntiLeech) + websiteRouter.POST("/leech/update", baseApi.UpdateAntiLeech) - groupRouter.POST("/redirect/update", baseApi.UpdateRedirectConfig) - groupRouter.POST("/redirect", baseApi.GetRedirectConfig) - groupRouter.POST("/redirect/file", baseApi.UpdateRedirectConfigFile) + websiteRouter.POST("/redirect/update", baseApi.UpdateRedirectConfig) + websiteRouter.POST("/redirect", baseApi.GetRedirectConfig) + websiteRouter.POST("/redirect/file", baseApi.UpdateRedirectConfigFile) + + websiteRouter.GET("/default/html/:type", baseApi.GetDefaultHtml) + websiteRouter.POST("/default/html/update", baseApi.UpdateDefaultHtml) } } diff --git a/cmd/server/nginx_conf/404.html b/cmd/server/nginx_conf/404.html new file mode 100644 index 000000000..d75ed7812 --- /dev/null +++ b/cmd/server/nginx_conf/404.html @@ -0,0 +1,6 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ \ No newline at end of file diff --git a/cmd/server/nginx_conf/domain404.html b/cmd/server/nginx_conf/domain404.html new file mode 100644 index 000000000..d75ed7812 --- /dev/null +++ b/cmd/server/nginx_conf/domain404.html @@ -0,0 +1,6 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ \ No newline at end of file diff --git a/cmd/server/nginx_conf/nginx_conf.go b/cmd/server/nginx_conf/nginx_conf.go index 5df2a0a30..7b376a9b0 100644 --- a/cmd/server/nginx_conf/nginx_conf.go +++ b/cmd/server/nginx_conf/nginx_conf.go @@ -28,3 +28,12 @@ var Proxy []byte //go:embed proxy_cache.conf var ProxyCache []byte + +//go:embed 404.html +var NotFoundHTML []byte + +//go:embed domain404.html +var DomainNotFoundHTML []byte + +//go:embed stop.html +var StopHTML []byte diff --git a/cmd/server/nginx_conf/stop.html b/cmd/server/nginx_conf/stop.html new file mode 100644 index 000000000..a38fa64b6 --- /dev/null +++ b/cmd/server/nginx_conf/stop.html @@ -0,0 +1,33 @@ + + + + + 抱歉,站点已暂停 + + + + +
+

抱歉!该站点已经被管理员停止运行,请联系管理员了解详情!

+
+ + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index fcd8be5f4..5d0799a22 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,9 @@ "prettier": "prettier --write ." }, "dependencies": { + "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-php": "^6.0.1", "@codemirror/language": "^6.10.2", "@codemirror/legacy-modes": "^6.4.0", "@codemirror/theme-one-dark": "^6.1.2", @@ -30,6 +32,7 @@ "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", "axios": "^1.7.2", + "codemirror": "^6.0.1", "echarts": "^5.5.0", "element-plus": "^2.7.5", "fit2cloud-ui-plus": "^1.1.4", diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 820fc63e2..095f3a5ec 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -519,4 +519,12 @@ export namespace Website { export interface SSLDownload { id: number; } + + export interface WebsiteHtml { + content: string; + } + export interface WebsiteHtmlUpdate { + type: string; + content: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index c30779f02..496910189 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -268,3 +268,11 @@ export const DownloadFile = (params: Website.SSLDownload) => { export const GetCA = (id: number) => { return http.get(`/websites/ca/${id}`); }; + +export const GetDefaultHtml = (type: string) => { + return http.get(`/websites/default/html/${type}`); +}; + +export const UpdateDefaultHtml = (req: Website.WebsiteHtmlUpdate) => { + return http.post(`/websites/default/html/update`, req); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 77f5c6239..7542980a7 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2032,6 +2032,12 @@ const message = { 'Only supports importing local backups, importing backups from other machines may cause recovery failure', ipWebsiteWarn: 'Websites with IP as domain names need to be set as default sites to be accessed normally', hstsHelper: 'Enabling HSTS can increase website security', + defaultHtml: 'Default page', + website404: 'Website 404 error page', + domain404: 'Website page does not exist', + indexHtml: 'Static website default page', + stopHtml: 'Website stop page', + indePhp: 'PHP website default page', sslExpireDate: 'Certificate expiration date', }, php: { diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 97a2f0fd3..c67659277 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1891,6 +1891,12 @@ const message = { websiteBackupWarn: '僅支援導入本機備份,導入其他機器備份可能會恢復失敗', ipWebsiteWarn: 'IP 為網域名稱的網站,需要設定為預設網站才能正常存取', hstsHelper: '開啟 HSTS 可以增加網站安全性', + defaultHtml: '預設頁面', + website404: '網站 404 錯誤頁', + domain404: '網站不存在頁面', + indexHtml: '靜態網站預設頁', + stopHtml: '網站停用頁', + indePhp: 'PHP 網站預設頁', sslExpireDate: '憑證過期時間', }, php: { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index e55c13407..2e32adacf 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1893,6 +1893,12 @@ const message = { websiteBackupWarn: '仅支持导入本机备份,导入其他机器备份可能会恢复失败', ipWebsiteWarn: 'IP 为域名的网站,需要设置为默认站点才能正常访问', hstsHelper: '开启 HSTS 可以增加网站安全性', + defaultHtml: '默认页面', + website404: '网站 404 错误页', + domain404: '网站不存在页', + indexHtml: '静态网站默认页', + stopHtml: '网站停用页', + indePhp: 'PHP 网站默认页', sslExpireDate: '证书过期时间', }, php: { diff --git a/frontend/src/views/website/website/html/index.vue b/frontend/src/views/website/website/html/index.vue new file mode 100644 index 000000000..7dcbb61c3 --- /dev/null +++ b/frontend/src/views/website/website/html/index.vue @@ -0,0 +1,107 @@ + + + + diff --git a/frontend/src/views/website/website/index.vue b/frontend/src/views/website/website/index.vue index 36620b3ee..ef5bfa111 100644 --- a/frontend/src/views/website/website/index.vue +++ b/frontend/src/views/website/website/index.vue @@ -30,6 +30,9 @@ {{ $t('website.defaultServer') }} + + {{ $t('website.defaultHtml') }} + @@ -182,6 +185,7 @@ + @@ -189,16 +193,17 @@ import Backups from '@/components/backup/index.vue'; import UploadDialog from '@/components/upload/index.vue'; import DefaultServer from '@/views/website/website/default/index.vue'; -import { onMounted, reactive, ref, computed } from '@vue/runtime-core'; -import CreateWebSite from './create/index.vue'; -import DeleteWebsite from './delete/index.vue'; +import DefaultHtml from '@/views/website/website/html/index.vue'; +import CreateWebSite from '@/views/website/website/create/index.vue'; +import DeleteWebsite from '@/views/website/website/delete/index.vue'; +import NginxConfig from '@/views/website/website/nginx/index.vue'; import GroupDialog from '@/components/group/index.vue'; -import { OpWebsite, SearchWebsites, UpdateWebsite } from '@/api/modules/website'; -import { Website } from '@/api/interface/website'; import AppStatus from '@/components/app-status/index.vue'; -import NginxConfig from './nginx/index.vue'; import i18n from '@/lang'; import router from '@/routers'; +import { onMounted, reactive, ref, computed } from '@vue/runtime-core'; +import { OpWebsite, SearchWebsites, UpdateWebsite } from '@/api/modules/website'; +import { Website } from '@/api/interface/website'; import { App } from '@/api/interface/app'; import { ElMessageBox } from 'element-plus'; import { dateFormatSimple } from '@/utils/util'; @@ -232,6 +237,7 @@ const maskShow = ref(true); const createRef = ref(); const deleteRef = ref(); const groupRef = ref(); +const defaultHtmlRef = ref(); const openNginxConfig = ref(false); const nginxIsExist = ref(false); const containerName = ref(''); @@ -418,6 +424,10 @@ const openDefault = () => { defaultRef.value.acceptParams(); }; +const openDefaultHtml = () => { + defaultHtmlRef.value.acceptParams(); +}; + const checkExist = (data: App.CheckInstalled) => { nginxIsExist.value = data.isExist; containerName.value = data.containerName;