Show posture values and report history
Pass/fail was the main device UI signal, but operators need the agent's observed value. Expose a formatted value per check, show current postures on the device page, and replace the Postures tab with paginated report history grouped by agent push time. Status stays in the model for later rulesets. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/itam"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
||||
@@ -64,6 +65,36 @@ func (r *deviceResolver) LatestPostures(ctx context.Context, obj *types.Device)
|
||||
return types.NewDevicePostures(postures), nil
|
||||
}
|
||||
|
||||
// PostureReports is the resolver for the postureReports field.
|
||||
func (r *deviceResolver) PostureReports(ctx context.Context, obj *types.Device, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DevicePostureReportOrderBy) (*types.DevicePostureReportConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, itam.ActionDevicePostureList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.DevicePostureReportOrderField]{
|
||||
Field: coredata.DevicePostureReportOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.DevicePostureReportOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
p, err := r.itam.ListPostureReports(ctx, scope, obj.ID, cursor)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot list device posture reports", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewDevicePostureReportConnection(p, r, obj.ID), nil
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the DeviceConnection.totalCount field.
|
||||
func (r *deviceConnectionResolver) TotalCount(ctx context.Context, obj *types.DeviceConnection) (int, error) {
|
||||
scope, err := r.authorize(ctx, obj.ParentID, itam.ActionDeviceList)
|
||||
@@ -85,6 +116,23 @@ func (r *deviceConnectionResolver) TotalCount(ctx context.Context, obj *types.De
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
// TotalCount is the resolver for the totalCount field.
|
||||
func (r *devicePostureReportConnectionResolver) TotalCount(ctx context.Context, obj *types.DevicePostureReportConnection) (int, error) {
|
||||
scope, err := r.authorize(ctx, obj.ParentID, itam.ActionDevicePostureList)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count, err := r.itam.CountPostureReports(ctx, scope, obj.ParentID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot count device posture reports", log.Error(err))
|
||||
|
||||
return 0, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// EnrollDevice is the resolver for the enrollDevice field.
|
||||
// SkipAssumptionCheck: self-enrollment from /enroll runs before the viewer
|
||||
// assumes the target organization.
|
||||
@@ -220,7 +268,13 @@ func (r *Resolver) DeviceConnection() schema.DeviceConnectionResolver {
|
||||
return &deviceConnectionResolver{r}
|
||||
}
|
||||
|
||||
// DevicePostureReportConnection returns schema.DevicePostureReportConnectionResolver implementation.
|
||||
func (r *Resolver) DevicePostureReportConnection() schema.DevicePostureReportConnectionResolver {
|
||||
return &devicePostureReportConnectionResolver{r}
|
||||
}
|
||||
|
||||
type (
|
||||
deviceResolver struct{ *Resolver }
|
||||
deviceConnectionResolver struct{ *Resolver }
|
||||
deviceResolver struct{ *Resolver }
|
||||
deviceConnectionResolver struct{ *Resolver }
|
||||
devicePostureReportConnectionResolver struct{ *Resolver }
|
||||
)
|
||||
|
||||
@@ -95,6 +95,55 @@ type Device implements Node {
|
||||
|
||||
owner: Profile @goField(forceResolver: true)
|
||||
latestPostures: [DevicePosture!]! @goField(forceResolver: true)
|
||||
postureReports(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: DevicePostureReportOrder
|
||||
): DevicePostureReportConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
enum DevicePostureValueKind
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKind"
|
||||
) {
|
||||
ON @goEnum(value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindOn")
|
||||
OFF
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindOff")
|
||||
IMMEDIATE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindImmediate"
|
||||
)
|
||||
SECONDS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindSeconds"
|
||||
)
|
||||
MIN_PASSWORD_LENGTH
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindMinPasswordLength"
|
||||
)
|
||||
CONFIGURED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindConfigured"
|
||||
)
|
||||
NONE
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindNone"
|
||||
)
|
||||
TEXT
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindText")
|
||||
UNKNOWN
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureValueKindUnknown"
|
||||
)
|
||||
}
|
||||
|
||||
type DevicePostureValue
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DevicePostureValue") {
|
||||
kind: DevicePostureValueKind!
|
||||
text: String!
|
||||
number: Int
|
||||
}
|
||||
|
||||
type DevicePosture implements Node {
|
||||
@@ -102,9 +151,48 @@ type DevicePosture implements Node {
|
||||
deviceId: ID!
|
||||
checkKey: String!
|
||||
status: DevicePostureStatus!
|
||||
value: DevicePostureValue!
|
||||
observedAt: Datetime!
|
||||
}
|
||||
|
||||
enum DevicePostureReportOrderField
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.DevicePostureReportOrderField"
|
||||
) {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.DevicePostureReportOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
input DevicePostureReportOrder
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DevicePostureReportOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: DevicePostureReportOrderField!
|
||||
}
|
||||
|
||||
type DevicePostureReport {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
postures: [DevicePosture!]!
|
||||
}
|
||||
|
||||
type DevicePostureReportConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DevicePostureReportConnection"
|
||||
) {
|
||||
edges: [DevicePostureReportEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
totalCount: Int! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type DevicePostureReportEdge {
|
||||
cursor: CursorKey!
|
||||
node: DevicePostureReport!
|
||||
}
|
||||
|
||||
type DeviceConnection
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DeviceConnection"
|
||||
|
||||
@@ -27,7 +27,8 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
DeviceOrderBy OrderBy[coredata.DeviceOrderField]
|
||||
DeviceOrderBy OrderBy[coredata.DeviceOrderField]
|
||||
DevicePostureReportOrderBy OrderBy[coredata.DevicePostureReportOrderField]
|
||||
|
||||
DeviceConnection struct {
|
||||
TotalCount int
|
||||
@@ -47,6 +48,15 @@ type (
|
||||
Cursor page.CursorKey
|
||||
Node *Device
|
||||
}
|
||||
|
||||
DevicePostureReportConnection struct {
|
||||
TotalCount int
|
||||
Edges []*DevicePostureReportEdge
|
||||
PageInfo PageInfo
|
||||
|
||||
Resolver any
|
||||
ParentID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func NewDeviceConnection(
|
||||
@@ -122,11 +132,14 @@ func NewDevice(d *coredata.Device) *Device {
|
||||
}
|
||||
|
||||
func NewDevicePosture(p *coredata.DevicePosture) *DevicePosture {
|
||||
value := coredata.ParseDevicePostureValue(p.CheckKey, p.Evidence)
|
||||
|
||||
return &DevicePosture{
|
||||
ID: p.ID,
|
||||
DeviceID: p.DeviceID,
|
||||
CheckKey: p.CheckKey,
|
||||
Status: p.Status,
|
||||
Value: &value,
|
||||
ObservedAt: p.ObservedAt,
|
||||
}
|
||||
}
|
||||
@@ -139,3 +152,41 @@ func NewDevicePostures(ps coredata.DevicePostures) []*DevicePosture {
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func NewDevicePostureReport(
|
||||
s *coredata.DevicePostureReport,
|
||||
) *DevicePostureReport {
|
||||
return &DevicePostureReport{
|
||||
ID: s.ID,
|
||||
CreatedAt: s.CreatedAt,
|
||||
Postures: NewDevicePostures(s.Postures),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDevicePostureReportEdge(
|
||||
s *coredata.DevicePostureReport,
|
||||
orderBy coredata.DevicePostureReportOrderField,
|
||||
) *DevicePostureReportEdge {
|
||||
return &DevicePostureReportEdge{
|
||||
Cursor: s.CursorKey(orderBy),
|
||||
Node: NewDevicePostureReport(s),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDevicePostureReportConnection(
|
||||
p *page.Page[*coredata.DevicePostureReport, coredata.DevicePostureReportOrderField],
|
||||
parentType any,
|
||||
parentID gid.GID,
|
||||
) *DevicePostureReportConnection {
|
||||
edges := make([]*DevicePostureReportEdge, len(p.Data))
|
||||
for i := range edges {
|
||||
edges[i] = NewDevicePostureReportEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &DevicePostureReportConnection{
|
||||
Edges: edges,
|
||||
PageInfo: *NewPageInfo(p),
|
||||
Resolver: parentType,
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user