mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-04 05:56:04 +08:00
parent
6b81ae1b7b
commit
6d4d53e2c7
17 changed files with 246 additions and 9 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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, "<p>")
|
||||
if len(parts) < 3 {
|
||||
return item
|
||||
}
|
||||
item.CreatedAt = strings.ReplaceAll(strings.TrimSpace(parts[1]), "</p>", "")
|
||||
for i := 1; i < len(parts); i++ {
|
||||
if strings.Contains(parts[i], "问题修复") {
|
||||
item.FixCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
if strings.Contains(parts[i], "新增功能") {
|
||||
item.NewCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
if strings.Contains(parts[i], "功能优化") {
|
||||
item.OptimizationCount = strings.Count(parts[i], "<li>")
|
||||
}
|
||||
}
|
||||
item.Content = strings.Replace(content, fmt.Sprintf("<p>%s</p>", 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ export const loadUpgradeInfo = () => {
|
|||
export const loadReleaseNotes = (version: string) => {
|
||||
return http.post<string>(`/core/settings/upgrade/notes`, { version: version });
|
||||
};
|
||||
export const listReleases = () => {
|
||||
return http.get<Array<Setting.ReleasesNotes>>(`/core/settings/upgrade/releases`);
|
||||
};
|
||||
export const upgrade = (version: string) => {
|
||||
return http.post(`/core/settings/upgrade`, { version: version });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 196 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -20,7 +20,7 @@
|
|||
<el-link underline="never" type="primary" @click="toLxware">
|
||||
{{ $t(!isMasterPro ? 'license.community' : 'license.pro') }}
|
||||
</el-link>
|
||||
<el-link underline="never" class="version" type="primary" @click="copyText(version)">
|
||||
<el-link underline="never" class="version" type="primary" @click="releasesRef.acceptParams()">
|
||||
{{ version }}
|
||||
</el-link>
|
||||
<el-badge is-dot class="-mt-0.5" :hidden="version === 'Waiting' || !globalStore.hasNewVersion">
|
||||
|
|
@ -36,15 +36,16 @@
|
|||
</div>
|
||||
|
||||
<Upgrade ref="upgradeRef" @search="search" />
|
||||
<Releases ref="releasesRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting';
|
||||
import Upgrade from '@/components/system-upgrade/upgrade/index.vue';
|
||||
import Releases from '@/components/system-upgrade/releases/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { copyText } from '@/utils/util';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
|
@ -52,6 +53,7 @@ import { storeToRefs } from 'pinia';
|
|||
const globalStore = GlobalStore();
|
||||
const { docsUrl } = storeToRefs(globalStore);
|
||||
const upgradeRef = ref();
|
||||
const releasesRef = ref();
|
||||
const isMasterPro = computed(() => {
|
||||
return globalStore.isMasterPro();
|
||||
});
|
||||
|
|
|
|||
104
frontend/src/components/system-upgrade/releases/index.vue
Normal file
104
frontend/src/components/system-upgrade/releases/index.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="$t('app.version')" @close="handleClose" size="large">
|
||||
<div class="note">
|
||||
<el-collapse v-model="currentVersion" :accordion="true" v-loading="loading">
|
||||
<div v-for="(item, index) in notes" :key="index">
|
||||
<el-collapse-item :name="index">
|
||||
<template #title>
|
||||
<div>
|
||||
<span class="version">{{ item.version }}</span>
|
||||
<span class="date">{{ item.createdAt }}</span>
|
||||
</div>
|
||||
<svg-icon class="icon" iconName="p-featureshitu"></svg-icon>
|
||||
<span class="icon-span">{{ item.newCount }}</span>
|
||||
<svg-icon class="icon" iconName="p-youhuawendang"></svg-icon>
|
||||
<span class="icon-span">{{ item.optimizationCount }}</span>
|
||||
<svg-icon class="icon" iconName="p-bug"></svg-icon>
|
||||
<span class="icon-span">{{ item.fixCount }}</span>
|
||||
</template>
|
||||
<div class="panel-MdEditor">
|
||||
<MdEditor v-model="item.content" previewOnly :theme="isDarkTheme ? 'dark' : 'light'" />
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</div>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listReleases } from '@/api/modules/setting';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { ref } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const currentVersion = ref(0);
|
||||
const notes = ref([]);
|
||||
const loading = ref();
|
||||
|
||||
const acceptParams = (): void => {
|
||||
search();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
await listReleases()
|
||||
.then((res) => {
|
||||
notes.value = res.data;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.version {
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
}
|
||||
.date {
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
.icon-span {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
}
|
||||
.panel-MdEditor {
|
||||
:deep(.md-editor-preview) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
:deep(.md-editor-dark) {
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
}
|
||||
.icon {
|
||||
font-size: 7px;
|
||||
margin-left: 50px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue