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