Skip to content

Commit 1ce369c

Browse files
feat: add ops report foundation (#12745)
* feat: add ops report foundation * chore: restore local vite proxy * chore: restore package lock * chore: move ops report i18n scope * chore: remove dashboard permission label * chore: nest ops report i18n * chore: update ops report menu i18n key * chore: sync permission locale cleanup
1 parent b3f41ca commit 1ce369c

21 files changed

Lines changed: 3341 additions & 13 deletions

File tree

core/app/api/v2/setting.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"net/http"
77
"os"
88
"path"
9+
"path/filepath"
910
"regexp"
11+
"strconv"
1012
"strings"
1113

1214
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
@@ -102,6 +104,50 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
102104
}
103105
req.Value = value
104106
}
107+
if req.Key == "OpsReportExportFormat" {
108+
if !global.CONF.Base.IsEnterprise {
109+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Key))
110+
return
111+
}
112+
if _, ok := constant.OpsReportExportFormats[req.Value]; !ok {
113+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Value))
114+
return
115+
}
116+
}
117+
if req.Key == "OpsReportSchedule" {
118+
if !global.CONF.Base.IsEnterprise {
119+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Key))
120+
return
121+
}
122+
if _, ok := constant.OpsReportSchedules[req.Value]; !ok {
123+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Value))
124+
return
125+
}
126+
}
127+
if req.Key == "OpsReportSavePath" {
128+
if !global.CONF.Base.IsEnterprise {
129+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Key))
130+
return
131+
}
132+
value := strings.TrimSpace(req.Value)
133+
if value == "" || !filepath.IsAbs(value) {
134+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", errors.New(req.Value))
135+
return
136+
}
137+
req.Value = filepath.Clean(value)
138+
}
139+
if req.Key == "OpsReportThreshold" {
140+
if !global.CONF.Base.IsEnterprise {
141+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrNotSupportType", errors.New(req.Key))
142+
return
143+
}
144+
threshold, err := strconv.Atoi(strings.TrimSpace(req.Value))
145+
if err != nil || threshold < 1 || threshold > 100 {
146+
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", errors.New(req.Value))
147+
return
148+
}
149+
req.Value = strconv.Itoa(threshold)
150+
}
105151

106152
if err := settingService.Update(c, req.Key, req.Value); err != nil {
107153
helper.InternalServer(c, err)

core/app/dto/setting.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ type SettingInfo struct {
4646
ProxyUser string `json:"proxyUser"`
4747
ProxyPasswd string `json:"proxyPasswd"`
4848
ProxyPasswdKeep string `json:"proxyPasswdKeep"`
49+
50+
OpsReportExportFormat string `json:"opsReportExportFormat"`
51+
OpsReportSchedule string `json:"opsReportSchedule"`
52+
OpsReportSavePath string `json:"opsReportSavePath"`
53+
OpsReportThreshold string `json:"opsReportThreshold"`
4954
}
5055

5156
type SettingBaseInfo struct {

core/app/service/setting.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,41 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
108108
}
109109
if !global.CONF.Base.IsEnterprise {
110110
info.IsOffline = constant.StatusDisable
111+
} else {
112+
u.ensureOpsReportSettingDefaults(&info)
111113
}
112114

113115
return &info, err
114116
}
115117

118+
func (u *SettingService) ensureOpsReportSettingDefaults(info *dto.SettingInfo) {
119+
if _, ok := constant.OpsReportExportFormats[info.OpsReportExportFormat]; !ok {
120+
info.OpsReportExportFormat = constant.OpsReportExportFormatPDF
121+
_ = settingRepo.UpdateOrCreate("OpsReportExportFormat", info.OpsReportExportFormat)
122+
}
123+
if _, ok := constant.OpsReportSchedules[info.OpsReportSchedule]; !ok {
124+
info.OpsReportSchedule = constant.OpsReportScheduleWeekly
125+
_ = settingRepo.UpdateOrCreate("OpsReportSchedule", info.OpsReportSchedule)
126+
}
127+
if strings.TrimSpace(info.OpsReportSavePath) == "" {
128+
info.OpsReportSavePath = defaultOpsReportSavePath()
129+
_ = settingRepo.UpdateOrCreate("OpsReportSavePath", info.OpsReportSavePath)
130+
}
131+
if !isValidOpsReportThreshold(info.OpsReportThreshold) {
132+
info.OpsReportThreshold = constant.OpsReportDefaultThreshold
133+
_ = settingRepo.UpdateOrCreate("OpsReportThreshold", info.OpsReportThreshold)
134+
}
135+
}
136+
137+
func defaultOpsReportSavePath() string {
138+
return path.Join(global.CONF.Base.InstallDir, constant.OpsReportDefaultSaveSubDir)
139+
}
140+
141+
func isValidOpsReportThreshold(value string) bool {
142+
threshold, err := strconv.Atoi(strings.TrimSpace(value))
143+
return err == nil && threshold >= 1 && threshold <= 100
144+
}
145+
116146
func (u *SettingService) GetSettingBaseInfo() (*dto.SettingBaseInfo, error) {
117147
setting, err := settingRepo.List()
118148
if err != nil {

core/constant/common.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ const (
1414
DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20
1515
DateTimeSlimLayout = "20060102150405"
1616

17+
OpsReportExportFormatPDF = "PDF"
18+
OpsReportExportFormatHTML = "HTML"
19+
OpsReportExportFormatMarkdown = "Markdown"
20+
OpsReportScheduleDaily = "daily"
21+
OpsReportScheduleWeekly = "weekly"
22+
OpsReportScheduleMonthly = "monthly"
23+
OpsReportDefaultThreshold = "80"
24+
OpsReportDefaultSaveSubDir = "1panel/data/ops-report"
25+
1726
OrderDesc = "descending"
1827
OrderAsc = "ascending"
1928

@@ -183,10 +192,31 @@ var WebUrlMap = map[string]struct{}{
183192
"/xpack/cluster/postgres": {},
184193
"/xpack/cluster/redis": {},
185194

186-
"/enterprise/users/list": {},
187-
"/enterprise/users/roles": {},
188-
"/enterprise/license": {},
189-
"/enterprise/license-required": {},
195+
"/enterprise/users/list": {},
196+
"/enterprise/users/roles": {},
197+
"/enterprise/license": {},
198+
"/enterprise/license-required": {},
199+
"/enterprise/ops-report": {},
200+
"/enterprise/ops-report/overview": {},
201+
"/enterprise/ops-report/system": {},
202+
"/enterprise/ops-report/login": {},
203+
"/enterprise/ops-report/website": {},
204+
"/enterprise/ops-report/resource": {},
205+
"/enterprise/ops-report/cronjob": {},
206+
"/enterprise/ops-report/history": {},
207+
"/enterprise/ops-report/settings": {},
208+
}
209+
210+
var OpsReportExportFormats = map[string]struct{}{
211+
OpsReportExportFormatPDF: {},
212+
OpsReportExportFormatHTML: {},
213+
OpsReportExportFormatMarkdown: {},
214+
}
215+
216+
var OpsReportSchedules = map[string]struct{}{
217+
OpsReportScheduleDaily: {},
218+
OpsReportScheduleWeekly: {},
219+
OpsReportScheduleMonthly: {},
190220
}
191221

192222
var DynamicRoutes = []string{

core/init/migration/helper/menu.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ func LoadMenus() string {
119119
Path: "/enterprise/users",
120120
Sort: 350,
121121
}, "NodeDashboard")
122+
item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{
123+
ID: "122",
124+
Disabled: false,
125+
Title: "xpack.opsReport.name",
126+
IsShow: true,
127+
Label: "OpsReport",
128+
Path: "/enterprise/ops-report",
129+
Sort: 360,
130+
}, "UserManagement")
122131
break
123132
}
124133
}
@@ -159,6 +168,7 @@ func MenuSort() []dto.MenuLabelSort {
159168
{Label: "Node", Sort: 300},
160169
{Label: "NodeDashboard", Sort: 300},
161170
{Label: "UserManagement", Sort: 350},
171+
{Label: "OpsReport", Sort: 360},
162172
{Label: "Upage", Sort: 400},
163173
{Label: "MonitorDashboard", Sort: 500},
164174
{Label: "Tamper", Sort: 600},

core/init/migration/migrate.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ func Init() {
3939
migrations.AddDocSourceSetting,
4040
migrations.AddAppStoreInstallAllowPortSetting,
4141
migrations.AddUserManagementMenu,
42+
migrations.AddOpsReportMenu,
43+
migrations.AddOpsReportSetting,
44+
migrations.AddOpsReportScheduleSetting,
45+
migrations.AddOpsReportThresholdSetting,
4246
migrations.AddAIBenchmarkMenu,
4347
migrations.AddAIProxyMenu,
4448
migrations.AddOperationLogUser,

core/init/migration/migrations/init.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,115 @@ var AddUserManagementMenu = &gormigrate.Migration{
11551155
},
11561156
}
11571157

1158+
var AddOpsReportMenu = &gormigrate.Migration{
1159+
ID: "20260512-add-ops-report-menu",
1160+
Migrate: func(tx *gorm.DB) error {
1161+
if !global.CONF.Base.IsEnterprise {
1162+
return nil
1163+
}
1164+
var menuJSON string
1165+
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
1166+
return err
1167+
}
1168+
if menuJSON == "" {
1169+
menuJSON = helper.LoadMenus()
1170+
}
1171+
1172+
var menus []dto.ShowMenu
1173+
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
1174+
return tx.Model(&model.Setting{}).
1175+
Where("key = ?", "HideMenu").
1176+
Update("value", helper.LoadMenus()).Error
1177+
}
1178+
1179+
newItem := dto.ShowMenu{
1180+
ID: "122",
1181+
Disabled: false,
1182+
Title: "xpack.opsReport.name",
1183+
IsShow: true,
1184+
Label: "OpsReport",
1185+
Path: "/enterprise/ops-report",
1186+
Sort: 360,
1187+
}
1188+
1189+
for i := range menus {
1190+
if menus[i].Label != "Xpack-Menu" {
1191+
continue
1192+
}
1193+
menus[i].Children = helper.UpsertMenuByLabel(menus[i].Children, newItem, "UserManagement")
1194+
break
1195+
}
1196+
1197+
updatedJSON, err := json.Marshal(menus)
1198+
if err != nil {
1199+
return tx.Model(&model.Setting{}).
1200+
Where("key = ?", "HideMenu").
1201+
Update("value", helper.LoadMenus()).Error
1202+
}
1203+
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
1204+
},
1205+
}
1206+
1207+
var AddOpsReportSetting = &gormigrate.Migration{
1208+
ID: "20260512-add-ops-report-setting",
1209+
Migrate: func(tx *gorm.DB) error {
1210+
if !global.CONF.Base.IsEnterprise {
1211+
return nil
1212+
}
1213+
var count int64
1214+
if err := tx.Model(&model.Setting{}).Where("key = ?", "OpsReportExportFormat").Count(&count).Error; err != nil {
1215+
return err
1216+
}
1217+
if count > 0 {
1218+
return nil
1219+
}
1220+
return tx.Create(&model.Setting{Key: "OpsReportExportFormat", Value: constant.OpsReportExportFormatPDF}).Error
1221+
},
1222+
}
1223+
1224+
var AddOpsReportScheduleSetting = &gormigrate.Migration{
1225+
ID: "20260513-add-ops-report-schedule-setting",
1226+
Migrate: func(tx *gorm.DB) error {
1227+
if !global.CONF.Base.IsEnterprise {
1228+
return nil
1229+
}
1230+
settings := []model.Setting{
1231+
{Key: "OpsReportSchedule", Value: constant.OpsReportScheduleWeekly},
1232+
{Key: "OpsReportSavePath", Value: path.Join(global.CONF.Base.InstallDir, constant.OpsReportDefaultSaveSubDir)},
1233+
}
1234+
for _, item := range settings {
1235+
var count int64
1236+
if err := tx.Model(&model.Setting{}).Where("key = ?", item.Key).Count(&count).Error; err != nil {
1237+
return err
1238+
}
1239+
if count > 0 {
1240+
continue
1241+
}
1242+
if err := tx.Create(&item).Error; err != nil {
1243+
return err
1244+
}
1245+
}
1246+
return nil
1247+
},
1248+
}
1249+
1250+
var AddOpsReportThresholdSetting = &gormigrate.Migration{
1251+
ID: "20260513-add-ops-report-threshold-setting",
1252+
Migrate: func(tx *gorm.DB) error {
1253+
if !global.CONF.Base.IsEnterprise {
1254+
return nil
1255+
}
1256+
var count int64
1257+
if err := tx.Model(&model.Setting{}).Where("key = ?", "OpsReportThreshold").Count(&count).Error; err != nil {
1258+
return err
1259+
}
1260+
if count > 0 {
1261+
return nil
1262+
}
1263+
return tx.Create(&model.Setting{Key: "OpsReportThreshold", Value: constant.OpsReportDefaultThreshold}).Error
1264+
},
1265+
}
1266+
11581267
var AddOperationLogUser = &gormigrate.Migration{
11591268
ID: "20260424-add-operation-log-user",
11601269
Migrate: func(tx *gorm.DB) error {

core/init/validator/validator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ var baseSettingKeys = map[string]struct{}{
4747
"AppStoreLastModified": {},
4848
"ScriptSync": {},
4949
"HideMenu": {},
50+
"OpsReportExportFormat": {},
51+
"OpsReportSchedule": {},
52+
"OpsReportSavePath": {},
53+
"OpsReportThreshold": {},
5054
}
5155

5256
func checkNamePattern(fl validator.FieldLevel) bool {

frontend/src/api/interface/log.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@ export namespace Log {
66
id: number;
77
source: string;
88
user: string;
9-
action: string;
9+
node: string;
1010
ip: string;
1111
path: string;
1212
method: string;
1313
userAgent: string;
14-
body: string;
15-
resp: string;
16-
17-
status: number;
14+
status: string;
1815
latency: number;
19-
errorMessage: string;
20-
21-
detail: string;
16+
message: string;
17+
detailZH: string;
18+
detailEN: string;
2219
createdAt: DateTimeFormats;
2320
}
2421
export interface SearchOpLog extends ReqPage {

frontend/src/api/interface/setting.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ export namespace Setting {
6969
proxyUser: string;
7070
proxyPasswd: string;
7171
proxyPasswdKeep: string;
72+
73+
opsReportExportFormat: string;
74+
opsReportSchedule: string;
75+
opsReportSavePath: string;
76+
opsReportThreshold: string;
7277
}
7378
export interface SettingBaseInfo {
7479
systemVersion: string;

0 commit comments

Comments
 (0)