feat: list pending package updates on the system page (#2427)

This commit is contained in:
Anand Hegde
2026-09-27 20:57:13 +05:30
committed by GitHub
parent c1505804bd
commit 8613cfe548
16 changed files with 1275 additions and 91 deletions

View File

@@ -202,6 +202,8 @@ func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
apiAuth.POST("/zfs/refresh", h.refreshZfsData).BindFunc(excludeReadOnlyRole)
// get systemd service details
apiAuth.GET("/systemd/info", h.getSystemdInfo)
// get pending package updates
apiAuth.GET("/package-updates", h.getPackageUpdates)
// /containers routes
if enabled, _ := utils.GetEnv("CONTAINER_DETAILS"); enabled != "false" {
// get container logs
@@ -445,6 +447,23 @@ func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
return e.JSON(http.StatusOK, map[string]any{"details": details})
}
// getPackageUpdates handles GET /api/beszel/package-updates requests
func (h *Hub) getPackageUpdates(e *core.RequestEvent) error {
systemID := e.Request.URL.Query().Get("system")
if systemID == "" {
return e.BadRequestError("Invalid system parameter", nil)
}
system, err := h.sm.GetSystem(systemID)
if err != nil || !system.HasUser(e.App, e.Auth) {
return e.NotFoundError("", nil)
}
updates, err := system.FetchPackageUpdatesFromAgent()
if err != nil {
return e.InternalServerError("", err)
}
return e.JSON(http.StatusOK, updates)
}
// refreshSmartData handles POST /api/beszel/smart/refresh requests
// Fetches fresh SMART data from the agent and updates the collection
func (h *Hub) refreshSmartData(e *core.RequestEvent) error {

View File

@@ -548,6 +548,59 @@ func TestApiRoutesAuthentication(t *testing.T) {
ExpectedContent: []string{"Something went wrong while processing your request."},
TestAppFactory: testAppFactory,
},
// /package-updates route
{
Name: "GET /package-updates - no auth should fail",
Method: http.MethodGet,
URL: fmt.Sprintf("/api/beszel/package-updates?system=%s", system.Id),
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /package-updates - missing system param should fail",
Method: http.MethodGet,
URL: "/api/beszel/package-updates",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"Invalid", "parameter"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /package-updates - invalid system should fail",
Method: http.MethodGet,
URL: "/api/beszel/package-updates?system=invalid-system",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 404,
ExpectedContent: []string{"The requested resource wasn't found."},
TestAppFactory: testAppFactory,
},
{
Name: "GET /package-updates - request for valid non-user system should fail",
Method: http.MethodGet,
URL: fmt.Sprintf("/api/beszel/package-updates?system=%s", system.Id),
ExpectedStatus: 404,
ExpectedContent: []string{"The requested resource wasn't found."},
TestAppFactory: testAppFactory,
Headers: map[string]string{
"Authorization": user2Token,
},
},
{
Name: "GET /package-updates - good user should pass validation",
Method: http.MethodGet,
URL: fmt.Sprintf("/api/beszel/package-updates?system=%s", system.Id),
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 500,
ExpectedContent: []string{"Something went wrong while processing your request."},
TestAppFactory: testAppFactory,
},
// /systemd routes
{
Name: "GET /systemd/info - no auth should fail",

View File

@@ -791,6 +791,15 @@ func (sys *System) FetchSmartDataFromAgent() (smart.SmartDataResponse, error) {
return result, err
}
// FetchPackageUpdatesFromAgent fetches the list of pending package updates from the agent.
func (sys *System) FetchPackageUpdatesFromAgent() (system.PackageUpdates, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var result system.PackageUpdates
err := sys.request(ctx, common.GetPackageUpdates, nil, &result)
return result, err
}
// FetchZfsDataFromAgent fetches ZFS detail data from the agent.
func (sys *System) FetchZfsDataFromAgent(force bool) (*zfs.ZfsData, error) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)