diff --git a/core/app/api/v2/upgrade.go b/core/app/api/v2/upgrade.go index 434ef3af9d0f..dc951afff3d5 100644 --- a/core/app/api/v2/upgrade.go +++ b/core/app/api/v2/upgrade.go @@ -21,6 +21,21 @@ func (b *BaseApi) GetUpgradeInfo(c *gin.Context) { helper.SuccessWithData(c, info) } +// @Tags System Setting +// @Summary Load upgrade notes +// @Success 200 {array} dto.ReleasesNotes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/upgrade/releases [get] +func (b *BaseApi) LoadRelease(c *gin.Context) { + notes, err := upgradeService.LoadRelease() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, notes) +} + // @Tags System Setting // @Summary Load release notes by version // @Accept json @@ -28,7 +43,7 @@ func (b *BaseApi) GetUpgradeInfo(c *gin.Context) { // @Success 200 {string} notes // @Security ApiKeyAuth // @Security Timestamp -// @Router /core/settings/upgrade [get] +// @Router /core/settings/upgrade/notes [post] func (b *BaseApi) GetNotesByVersion(c *gin.Context) { var req dto.Upgrade if err := helper.CheckBindAndValidate(&req, c); err != nil { diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go index 931b3f0ceb31..3ab501c4b9ab 100644 --- a/core/app/dto/setting.go +++ b/core/app/dto/setting.go @@ -145,6 +145,15 @@ type Upgrade struct { Version string `json:"version" validate:"required"` } +type ReleasesNotes struct { + Version string `json:"version"` + CreatedAt string `json:"createdAt"` + Content string `json:"content"` + NewCount int `json:"newCount"` + OptimizationCount int `json:"optimizationCount"` + FixCount int `json:"fixCount"` +} + type ProxyUpdate struct { ProxyUrl string `json:"proxyUrl"` ProxyType string `json:"proxyType"` diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go index 4716685f707b..eda8e0f45145 100644 --- a/core/app/service/upgrade.go +++ b/core/app/service/upgrade.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "fmt" + "io" "net/http" "os" "path" @@ -29,6 +30,7 @@ type IUpgradeService interface { Rollback(req dto.OperateByID) error LoadNotes(req dto.Upgrade) (string, error) SearchUpgrade() (*dto.UpgradeInfo, error) + LoadRelease() ([]dto.ReleasesNotes, error) } func NewIUpgradeService() IUpgradeService { @@ -208,6 +210,66 @@ func (u *UpgradeService) Rollback(req dto.OperateByID) error { return nil } +type noteHelper struct { + Docs []noteDetailHelper `json:"docs"` +} +type noteDetailHelper struct { + Location string `json:"location"` + Text string `json:"text"` + Title string `json:"title"` +} + +func (u *UpgradeService) LoadRelease() ([]dto.ReleasesNotes, error) { + var notes []dto.ReleasesNotes + resp, err := req_helper.HandleGet("https://1panel.cn/docs/v2/search/search_index.json") + if err != nil { + return notes, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return notes, err + } + defer resp.Body.Close() + var nodeItem noteHelper + if err := json.Unmarshal(body, &nodeItem); err != nil { + return notes, err + } + for _, item := range nodeItem.Docs { + if !strings.HasPrefix(item.Location, "changelog/#v") { + continue + } + itemNote := analyzeDoc(item.Title, item.Text) + if len(itemNote.CreatedAt) != 0 { + notes = append(notes, analyzeDoc(item.Title, item.Text)) + } + } + + return notes, nil +} + +func analyzeDoc(version, content string) dto.ReleasesNotes { + var item dto.ReleasesNotes + parts := strings.Split(content, "

") + if len(parts) < 3 { + return item + } + item.CreatedAt = strings.ReplaceAll(strings.TrimSpace(parts[1]), "

", "") + for i := 1; i < len(parts); i++ { + if strings.Contains(parts[i], "问题修复") { + item.FixCount = strings.Count(parts[i], "
  • ") + } + if strings.Contains(parts[i], "新增功能") { + item.NewCount = strings.Count(parts[i], "
  • ") + } + if strings.Contains(parts[i], "功能优化") { + item.OptimizationCount = strings.Count(parts[i], "
  • ") + } + } + item.Content = strings.Replace(content, fmt.Sprintf("

    %s

    ", item.CreatedAt), "", 1) + item.Version = version + return item +} + func (u *UpgradeService) handleBackup(originalDir string) error { if err := files.CopyItem(false, true, "/usr/local/bin/1panel-core", originalDir); err != nil { return err diff --git a/core/router/ro_setting.go b/core/router/ro_setting.go index b640f9e09d8b..7ba82d27eeaa 100644 --- a/core/router/ro_setting.go +++ b/core/router/ro_setting.go @@ -38,6 +38,7 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { settingRouter.POST("/upgrade", baseApi.Upgrade) settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion) + settingRouter.GET("/upgrade/releases", baseApi.LoadRelease) settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo) settingRouter.POST("/api/config/generate/key", baseApi.GenerateApiKey) settingRouter.POST("/api/config/update", baseApi.UpdateApiConfig) diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index e22db8876ebd..b55871c8efc3 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -241,6 +241,14 @@ export namespace Setting { isBound: boolean; name: string; } + export interface ReleasesNotes { + Version: string; + CreatedAt: string; + Content: string; + NewCount: number; + OptimizationCount: number; + FixCount: number; + } export interface LicenseBind { nodeID: number; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 353a34709776..ad94db2fb453 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -170,6 +170,9 @@ export const loadUpgradeInfo = () => { export const loadReleaseNotes = (version: string) => { return http.post(`/core/settings/upgrade/notes`, { version: version }); }; +export const listReleases = () => { + return http.get>(`/core/settings/upgrade/releases`); +}; export const upgrade = (version: string) => { return http.post(`/core/settings/upgrade`, { version: version }); }; diff --git a/frontend/src/assets/iconfont/iconfont.css b/frontend/src/assets/iconfont/iconfont.css index 28afd2ec1c70..604025f65360 100644 --- a/frontend/src/assets/iconfont/iconfont.css +++ b/frontend/src/assets/iconfont/iconfont.css @@ -1,9 +1,9 @@ @font-face { font-family: "iconfont"; /* Project id 4776196 */ - src: url('iconfont.woff2?t=1752473267421') format('woff2'), - url('iconfont.woff?t=1752473267421') format('woff'), - url('iconfont.ttf?t=1752473267421') format('truetype'), - url('iconfont.svg?t=1752473267421#iconfont') format('svg'); + src: url('iconfont.woff2?t=1755181498446') format('woff2'), + url('iconfont.woff?t=1755181498446') format('woff'), + url('iconfont.ttf?t=1755181498446') format('truetype'), + url('iconfont.svg?t=1755181498446#iconfont') format('svg'); } .iconfont { @@ -14,6 +14,14 @@ -moz-osx-font-smoothing: grayscale; } +.p-featureshitu:before { + content: "\e63e"; +} + +.p-youhuawendang:before { + content: "\e7c4"; +} + .p-cluster-3:before { content: "\e706"; } diff --git a/frontend/src/assets/iconfont/iconfont.js b/frontend/src/assets/iconfont/iconfont.js index 2fe784eed0b7..c21755d944d2 100644 --- a/frontend/src/assets/iconfont/iconfont.js +++ b/frontend/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4776196='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,p,z,v,i=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4776196,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?i(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=h,z=a.document,v=!1,M(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,d())})}function d(){v||(v=!0,p())}function M(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(M,50)}d()}})(window); \ No newline at end of file +window._iconfont_svg_string_4776196='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,p,z,v,i=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4776196,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?i(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(p=h,z=a.document,v=!1,M(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,d())})}function d(){v||(v=!0,p())}function M(){try{z.documentElement.doScroll("left")}catch(l){return void setTimeout(M,50)}d()}})(window); \ No newline at end of file diff --git a/frontend/src/assets/iconfont/iconfont.json b/frontend/src/assets/iconfont/iconfont.json index b5da8c18efe2..52426c5dc9e6 100644 --- a/frontend/src/assets/iconfont/iconfont.json +++ b/frontend/src/assets/iconfont/iconfont.json @@ -5,6 +5,20 @@ "css_prefix_text": "p-", "description": "", "glyphs": [ + { + "icon_id": "18536446", + "name": "feature视图", + "font_class": "featureshitu", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "18133027", + "name": "优化文档", + "font_class": "youhuawendang", + "unicode": "e7c4", + "unicode_decimal": 59332 + }, { "icon_id": "88609", "name": "cluster-3", diff --git a/frontend/src/assets/iconfont/iconfont.svg b/frontend/src/assets/iconfont/iconfont.svg index a2ace5801e2e..8c1eacd3597c 100644 --- a/frontend/src/assets/iconfont/iconfont.svg +++ b/frontend/src/assets/iconfont/iconfont.svg @@ -14,6 +14,10 @@ /> + + + + diff --git a/frontend/src/assets/iconfont/iconfont.ttf b/frontend/src/assets/iconfont/iconfont.ttf index ca60bd0e9a1f..af631a2321b2 100644 Binary files a/frontend/src/assets/iconfont/iconfont.ttf and b/frontend/src/assets/iconfont/iconfont.ttf differ diff --git a/frontend/src/assets/iconfont/iconfont.woff b/frontend/src/assets/iconfont/iconfont.woff index ee592ad850c3..cd43b036a2b1 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff and b/frontend/src/assets/iconfont/iconfont.woff differ diff --git a/frontend/src/assets/iconfont/iconfont.woff2 b/frontend/src/assets/iconfont/iconfont.woff2 index dcc613fdf43e..54396194ffc4 100644 Binary files a/frontend/src/assets/iconfont/iconfont.woff2 and b/frontend/src/assets/iconfont/iconfont.woff2 differ diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index b95a1c617dc4..5c3831d78601 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -20,7 +20,7 @@ {{ $t(!isMasterPro ? 'license.community' : 'license.pro') }} - + {{ version }} @@ -36,15 +36,16 @@ + + + diff --git a/frontend/src/styles/element-dark.scss b/frontend/src/styles/element-dark.scss index 7073d4a98d3f..a50e3b0d49a9 100644 --- a/frontend/src/styles/element-dark.scss +++ b/frontend/src/styles/element-dark.scss @@ -462,7 +462,8 @@ html.dark { .el-collapse-item__header { color: #ffffff; - background-color: var(--panel-main-bg-color-10) !important; + border: 1px solid var(--panel-main-bg-color-10); + background-color: var(--panel-main-bg-color-9) !important; } .el-checkbox__input.is-checked .el-checkbox__inner::after { diff --git a/frontend/src/styles/element.scss b/frontend/src/styles/element.scss index 63e31cbf02d7..dcbbe55da54f 100644 --- a/frontend/src/styles/element.scss +++ b/frontend/src/styles/element.scss @@ -270,4 +270,10 @@ html { color: var(--el-color-primary) !important; background-color: var(--el-color-primary-light-9) !important; border-color: var(--el-button-border-color) !important; +} + +.el-collapse-item__header { + color: var(--el-text-color-regular) !important; + border: 1px solid var(--panel-main-bg-color-10); + background-color: var(--panel-main-bg-color-10) !important; } \ No newline at end of file