mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 12:42:49 +00:00
b5444cf8bf
The vSphere Activity tab titled every vCenter event with its class name
("HostConnectedEvent", "HostProfileAppliedEvent"), and its When column
clipped the time on every row at most widths.
Pulse never asked vCenter for the readable message: QueryEvents leaves
out fullFormattedMessage unless the filter sets disableFullMessage, so
on a real vCenter the drawer's Message row was always empty too. The
client now sends disableFullMessage: false, and the provider titles an
event change with that message, falling back to the class. That also
gives Patrol's change context a readable reason. The page model prefers
the message for rows stored before this change, and keeps the class as
an Event type row in the drawer and in search.
When now reads as an age ("1d ago"), as Kubernetes events do, with the
exact time on hover and in the drawer. A vCenter time before 2000 (its
unset marker) still shows as "-". The Activity table moves from the
date-time registry rule to the relative-time rule.
123 lines
4.8 KiB
Go
123 lines
4.8 KiB
Go
package vmware
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
)
|
|
|
|
// ActivityChanges returns canonical shared-timeline activity derived from the
|
|
// cached VMware snapshot.
|
|
func (p *Provider) ActivityChanges() []unifiedresources.ResourceChange {
|
|
if p == nil || !IsFeatureEnabled() {
|
|
return nil
|
|
}
|
|
|
|
snapshot := p.Snapshot()
|
|
return activityChangesFromSnapshot(snapshot)
|
|
}
|
|
|
|
// FixtureActivityChanges projects VMware fixture activity into canonical
|
|
// timeline changes without consulting the runtime feature flag.
|
|
func FixtureActivityChanges(snapshot InventorySnapshot) []unifiedresources.ResourceChange {
|
|
return activityChangesFromSnapshot(&snapshot)
|
|
}
|
|
|
|
func activityChangesFromSnapshot(snapshot *InventorySnapshot) []unifiedresources.ResourceChange {
|
|
if snapshot == nil {
|
|
return nil
|
|
}
|
|
|
|
changes := make([]unifiedresources.ResourceChange, 0)
|
|
for _, host := range snapshot.Hosts {
|
|
resourceID := vmwareSourceID(snapshot.ConnectionID, "host", host.Host)
|
|
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "host", host.Host, host.RecentTasks, host.RecentEvents)...)
|
|
}
|
|
for _, vm := range snapshot.VMs {
|
|
resourceID := vmwareSourceID(snapshot.ConnectionID, "vm", vm.VM)
|
|
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "vm", vm.VM, vm.RecentTasks, vm.RecentEvents)...)
|
|
}
|
|
for _, datastore := range snapshot.Datastores {
|
|
resourceID := vmwareSourceID(snapshot.ConnectionID, "datastore", datastore.Datastore)
|
|
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "datastore", datastore.Datastore, datastore.RecentTasks, datastore.RecentEvents)...)
|
|
}
|
|
for _, network := range snapshot.Networks {
|
|
resourceID := vmwareSourceID(snapshot.ConnectionID, "network", network.Network)
|
|
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "network", network.Network, network.RecentTasks, network.RecentEvents)...)
|
|
}
|
|
|
|
sort.SliceStable(changes, func(i, j int) bool {
|
|
if !changes[i].ObservedAt.Equal(changes[j].ObservedAt) {
|
|
return changes[i].ObservedAt.After(changes[j].ObservedAt)
|
|
}
|
|
return changes[i].ID > changes[j].ID
|
|
})
|
|
return changes
|
|
}
|
|
|
|
func entityActivityChanges(resourceID, connectionID, entityType, managedObjectID string, tasks []InventoryTask, events []InventoryEvent) []unifiedresources.ResourceChange {
|
|
out := make([]unifiedresources.ResourceChange, 0, len(tasks)+len(events))
|
|
for _, task := range tasks {
|
|
change := unifiedresources.BuildPlatformActivityChange(resourceID, unifiedresources.PlatformActivityChange{
|
|
SourceAdapter: unifiedresources.AdapterVMware,
|
|
ActivityType: "vmware_task",
|
|
NativeID: strings.TrimSpace(task.Task),
|
|
Title: strings.TrimSpace(task.Name),
|
|
State: strings.TrimSpace(task.State),
|
|
Message: strings.TrimSpace(task.ErrorMessage),
|
|
OccurredAt: firstNonZeroTime(task.CompletedAt, task.StartedAt),
|
|
Metadata: map[string]any{
|
|
"vmwareConnectionId": strings.TrimSpace(connectionID),
|
|
"vmwareEntityType": strings.TrimSpace(entityType),
|
|
"vmwareManagedObjectId": strings.TrimSpace(managedObjectID),
|
|
"vmwareTask": strings.TrimSpace(task.Task),
|
|
"vmwareTaskName": strings.TrimSpace(task.Name),
|
|
"vmwareTaskState": strings.TrimSpace(task.State),
|
|
"vmwareTaskDescription": strings.TrimSpace(task.DescriptionID),
|
|
"vmwareTaskError": strings.TrimSpace(task.ErrorMessage),
|
|
},
|
|
})
|
|
if change != nil {
|
|
out = append(out, *change)
|
|
}
|
|
}
|
|
for _, event := range events {
|
|
change := unifiedresources.BuildPlatformActivityChange(resourceID, unifiedresources.PlatformActivityChange{
|
|
SourceAdapter: unifiedresources.AdapterVMware,
|
|
ActivityType: "vmware_event",
|
|
NativeID: strings.TrimSpace(event.Event),
|
|
// vCenter's own sentence ("Host esxi-01 in Primary DC is connected")
|
|
// reads where the event class (HostConnectedEvent) does not; the
|
|
// class stays in vmwareEventType for search and the drawer.
|
|
Title: firstNonEmptyTrimmed(event.Message, event.Type),
|
|
Message: strings.TrimSpace(event.Message),
|
|
Actor: strings.TrimSpace(event.User),
|
|
OccurredAt: event.CreatedAt,
|
|
Metadata: map[string]any{
|
|
"vmwareConnectionId": strings.TrimSpace(connectionID),
|
|
"vmwareEntityType": strings.TrimSpace(entityType),
|
|
"vmwareManagedObjectId": strings.TrimSpace(managedObjectID),
|
|
"vmwareEvent": strings.TrimSpace(event.Event),
|
|
"vmwareEventType": strings.TrimSpace(event.Type),
|
|
"vmwareEventMessage": strings.TrimSpace(event.Message),
|
|
"vmwareEventUser": strings.TrimSpace(event.User),
|
|
},
|
|
})
|
|
if change != nil {
|
|
out = append(out, *change)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func firstNonZeroTime(values ...time.Time) time.Time {
|
|
for _, value := range values {
|
|
if !value.IsZero() {
|
|
return value.UTC()
|
|
}
|
|
}
|
|
return time.Time{}
|
|
}
|