Repository navigation
workspace sdk report pages
A custom report page displays tabular data inside the standard Security Desk reporting interface. The reporting interface provides built-in support for filtering, sorting, charting, tile display, and export.
The client-side ReportPage defines the columns and filters. When the operator clicks Generate, the framework sends a CustomQuery to the server-side plugin. The plugin builds a DataTable and returns the rows.
Use a custom report page when operators need the built-in reporting experience with custom server-backed data, filters, charts, or export instead of a generic page or task.
Important
The server-side plugin must include ReportQueryType.Custom in SupportedQueries and the report GUID in SupportedCustomReports. See About plugin queries for handling queries, returning rows, and signaling completion.
ReportPage extends TilePage, which extends Page. It follows the page lifecycle described in About pages, including activation, deactivation, and disposal. However, custom report tasks do not use the generic page Serialize() and Deserialize(byte[]) hooks when the operator saves and reopens the report. To persist custom report state, use the report filter guidance on this page.
Note
Report pages do not require an SDK certificate. The framework skips certificate validation for ReportPage subclasses.
- The report page class has a
[Page]attribute pointing to its descriptor. The module registers aCreatePageTask<T>for the report page type inLoad(). - When the operator opens the task, the framework creates the report page and calls
Initialize(Workspace). -
Initialize()is sealed inReportPage. It prepares the report page for the custom report task. - The framework reads
Fieldsto build the report columns, and reads the filter properties to configure the filter panel. - The operator configures filters and clicks Generate.
- The framework sends a
CustomQueryto the server-side plugin that matches the report'sPageDescriptor.Type. - The plugin returns a
DataTablewith rows matching the field names. - The reporting interface displays the results in the table, with optional charts and tile display.
Override Fields to define the columns that appear in the report table. Each ReportField specifies a column name, display name, data type, and display options:
public override List<ReportField> Fields => new List<ReportField>
{
new ReportField { Type = ReportFieldType.Entity, Name = "Source", DisplayName = "Source", IsSource = true },
new ReportField { Type = ReportFieldType.DateTime, Name = "Timestamp", DisplayName = "Timestamp", InitialWidth = 180 },
new ReportField { Type = ReportFieldType.Event, Name = "EventType", DisplayName = "Event" },
new ReportField { Type = ReportFieldType.Text, Name = "Description", DisplayName = "Description" },
};| Member | Type | Description |
|---|---|---|
Type |
ReportFieldType | Data type for the column. Determines how the framework formats and sorts the value. |
Name |
string | Unique column identifier without periods. Must match the column name in the DataTable returned by the plugin, including capitalization. |
DisplayName |
string | Column header text shown in the report table. |
IsSource |
bool | Marks this field as a source for tile display. See below. Defaults to false. |
Converter |
IValueConverter | WPF value converter to transform the raw data for display and export. The framework passes the DataRow as the value. The converter is called both when rendering cells in the report table and when exporting to PDF or Excel. |
InitialWidth |
int | Default column width in pixels. |
IsVisible |
bool | Whether the column is visible by default. Defaults to true. Operators can show hidden columns through the column selection menu. Hidden columns are excluded from exports but remain accessible in EditRow through the DataRow. Use hidden columns for data that supports sorting or row editing without cluttering the report table. |
ImageMaxHeight |
int | Maximum image height in pixels. Applies only when Type is Image. |
ImageMaxWidth |
int | Maximum image width in pixels. Applies only when Type is Image. |
| Value | Description |
|---|---|
Entity |
An entity reference. The framework resolves the entity name for display. |
DateTime |
A date and time value. Values are interpreted as UTC and displayed in local time. |
Event |
An event type. The framework resolves the event name for display. |
Boolean |
A boolean value. |
Text |
A text string. |
Numeric |
A double-precision numeric value. |
Decimal |
A number with a decimal place. |
Image |
An image. Use ImageMaxHeight and ImageMaxWidth to constrain the display size. |
TimeSpan |
A time duration. |
When IsSource is true, the framework uses the field's value to drive tile content. The behavior depends on the field type:
-
Entityfields populate the source entity GUID. When the operator clicks a result row, the tile displays this entity. -
DateTimefields populate the event timestamp used for playback positioning. -
Eventfields populate the event type for the tile overlay.
Set IsSource to true for at most one Entity field, one DateTime field, and one Event field.
ReportPage provides virtual properties that toggle built-in filter panels. Override these properties to display the filters relevant to your report:
| Property | Type | Default | Description |
|---|---|---|---|
DisplayEntityFilter |
bool | false |
Show the entity filter panel. |
DisplayEventFilter |
bool | false |
Show the event type filter panel. |
DisplayTimeRangeFilter |
bool | false |
Show the time range filter panel. |
EntityTypes |
List<EntityType> | null |
Entity types available in the entity filter. Only used when DisplayEntityFilter is true. |
EventFilterEntityTypes |
List<EntityType> | null |
Entity types whose associated events appear in the event filter. Only used when DisplayEventFilter is true. |
EventFilterExcluded |
List<EventType> | null |
Event types to exclude from the event filter. Only used when DisplayEventFilter is true. |
protected override bool DisplayEntityFilter => true;
protected override bool DisplayEventFilter => true;
protected override bool DisplayTimeRangeFilter => true;
protected override List<EntityType> EntityTypes => new List<EntityType> { EntityType.Door };
protected override List<EntityType> EventFilterEntityTypes => new List<EntityType> { EntityType.Door };
protected override List<EventType> EventFilterExcluded => new List<EventType> { EventType.DoorClose };For filtering criteria beyond entities, events, and time ranges, override CustomFilter to provide a custom filter control. The custom filter appears alongside the built-in filters in the filter panel.
Use FilterData to serialize the custom filter state. The framework sends this string to the plugin when the operator runs the report, and restores the same value when the operator reopens a saved report task. Implement the getter to return the current filter state and the setter to restore it.
Create a custom filter by inheriting from ReportFilter, which is a WPF UserControl:
<Pages:ReportFilter
x:Class="MyModule.MyReportFilter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Pages="clr-namespace:Genetec.Sdk.Workspace.Pages;assembly=Genetec.Sdk.Workspace">
<StackPanel Margin="4">
<TextBlock Text="Priority:" />
<ComboBox ItemsSource="{Binding Priorities}"
SelectedValue="{Binding Priority, Mode=TwoWay}" />
</StackPanel>
</Pages:ReportFilter>public partial class MyReportFilter : ReportFilter
{
private int m_priority;
public MyReportFilter()
{
InitializeComponent();
DataContext = this;
}
public IReadOnlyList<int> Priorities { get; } = new[] { 1, 2, 3, 4, 5 };
public int Priority
{
get => m_priority;
set { m_priority = value; OnModified(); }
}
protected override string FilterName => "Priority Filter";
protected override bool IsMandatory => true;
protected override bool IsStateValid => Priority > 0;
protected override string FilterData
{
get => Priority.ToString();
set => Priority = int.TryParse(value, out var p) ? p : 0;
}
}Call OnModified() whenever the filter state changes to notify the framework. In this example, setting Priority calls OnModified(), which tells the framework to re-read FilterData and IsStateValid. Because IsMandatory is true, the Generate button is disabled until Priority is greater than zero.
| Member | Type | Description |
|---|---|---|
FilterName |
string (virtual) | Display name shown on the filter panel header. |
FilterData |
string (virtual) | Current filter state as a serialized string. Included in the query sent to the plugin and persisted when the operator saves the report as a task. |
IsMandatory |
bool (virtual) | When true, the report cannot be generated if IsStateValid returns false. Defaults to false. |
IsStateValid |
bool (virtual) | Whether the current filter state is valid. When false and IsMandatory is true, the Generate button is disabled. Defaults to true. |
IsApplied |
bool | Whether the filter is currently applied. Defaults to true. |
Workspace |
Workspace | The workspace instance. Available after Initialize(Workspace) is called. |
Override the protected Initialize() method to perform custom setup after the workspace is available.
Then return the filter from your report page:
protected override ReportFilter CustomFilter => new MyReportFilter();Operators can save a custom report as a public or private task when the report is eligible and the current operator has the required privileges.
Even though ReportPage derives from Page, the framework does not call Serialize() or Deserialize(byte[]) on the report page when it saves or restores a custom report task.
To persist custom state in a saved report task, store it in ReportFilter.FilterData. The framework saves the filter state and restores it when the operator opens the task again. If your report does not define custom filter state, you do not need to add extra serialization code on the report page.
Do not override CanSave(), Serialize(), or Deserialize(byte[]) on the report page for this scenario. Those hooks apply to generic pages described in About pages, not to custom report task state.
Call OnModified() whenever the filter changes so the framework re-reads FilterData and IsStateValid before the operator generates or saves the report.
Control whether the report supports chart and tile visualizations:
| Property | Type | Default | Description |
|---|---|---|---|
SupportsCharts |
bool | true |
Whether the report results can be displayed as a chart. |
SupportsTiles |
bool | true |
Whether the report results can be displayed on tiles. |
public override bool SupportsTiles => false;Override EditRow to handle double-click on a report row. The ReportPageContext contains the selected rows as DataRow objects:
protected override bool EditRow(ReportPageContext context)
{
var row = context.SelectedRows.FirstOrDefault();
if (row == null)
return false;
var entityId = (Guid)row["Source"];
// Open entity details or perform an action
return true;
}Return true if the action was handled, false otherwise.
Call Generate() on a report page instance to execute the query without user interaction. Generate() runs the query with the current filter state and does not check IsMandatory or IsStateValid, so set the filter values you need before you call it:
var page = new MyReport();
page.Initialize(Workspace);
Workspace.ActiveMonitor.Pages.Add(page);
Workspace.ActiveMonitor.ActivePage = page;
page.Generate();Override CustomSortDescriptionCollection to define a default sort order for the report results:
protected override SortDescriptionCollection CustomSortDescriptionCollection
{
get
{
var collection = new SortDescriptionCollection();
collection.Add(new SortDescription("Timestamp", ListSortDirection.Descending));
return collection;
}
}Use IReportService to open a built-in report type and prepopulate its filters. Pass the report type GUID, a display name, and a FieldsCollection containing the initial filter values:
var reportService = Workspace.Services.Get<IReportService>();
var fields = new FieldsCollection();
fields.Add("EntityId", doorGuid);
ReportPage page = reportService.CreateReport(reportTypeId, "Door Activity", fields);An async overload is also available:
ReportPage page = await reportService.CreateReportAsync(reportTypeId, "Door Activity", fields, cancellationToken);Register the report page by applying the [Page] attribute to the report page class and creating a CreatePageTask<T> in your module's Load method. The descriptor's Type GUID must match the report ID declared in the server-side plugin's SupportedCustomReports property.
The [Page] attribute serves two purposes: it associates the descriptor with the page class, and it makes the report page discoverable by the framework. The client scans deployed Workspace SDK module assemblies for classes with [Page] attributes to find custom report page types. This scanning is independent of the module's Load() method. The server-side plugin assembly that answers the query is deployed separately, on the Security Center servers that run the plugin.
CreatePageTask<T> adds the report task to the Security Desk home page so operators can open it interactively. For details on page descriptors and task categories, see About pages.
[Page(typeof(MyReportDescriptor))]
public class MyReport : ReportPage
{
public override List<ReportField> Fields => new List<ReportField>
{
new ReportField { Type = ReportFieldType.Entity, Name = "Source", DisplayName = "Source", IsSource = true },
new ReportField { Type = ReportFieldType.DateTime, Name = "Timestamp", DisplayName = "Timestamp", IsSource = true },
new ReportField { Type = ReportFieldType.Text, Name = "Description", DisplayName = "Description" },
};
protected override bool DisplayEntityFilter => true;
protected override List<EntityType> EntityTypes => new List<EntityType> { EntityType.Door };
public override bool SupportsTiles => false;
}
public class MyReportDescriptor : PageDescriptor
{
public override string Name => "My Custom Report";
public override Guid Type => new Guid("REPORT-TYPE-GUID");
public override Guid CategoryId => new Guid(TaskCategories.Investigation);
public override ImageSource Thumbnail => new BitmapImage(new Uri(@"pack://application:,,,/MY-ASSEMBLY;Component/Resources/MY-THUMBNAIL.png", UriKind.RelativeOrAbsolute));
}public class SampleModule : Module
{
private CreatePageTask<MyReport> m_reportTask;
public override void Load()
{
if (Workspace.ApplicationType != ApplicationType.SecurityDesk)
return;
m_reportTask = new CreatePageTask<MyReport>(isSingleton: true);
m_reportTask.Initialize(Workspace);
Workspace.Tasks.Register(m_reportTask);
}
public override void Unload()
{
if (m_reportTask != null)
Workspace.Tasks.Unregister(m_reportTask);
}
}The Report Manager role loads custom modules when executing scheduled report tasks. In this context, Workspace.ApplicationType is ApplicationType.None. The ApplicationType check in the example above prevents the module from registering a CreatePageTask (which requires a home page), but the framework still discovers the report page type through the [Page] attribute on the class.
- About the Workspace SDK: Workspace SDK architecture and module lifecycle.
- About pages: Custom pages displayed in monitor windows.
- About tasks: Adding executable menu items and home tasks.
- About workspace services: Built-in workspace services and how to consume them.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- ASP.NET hosting
- SDK compatibility
- Migrating to modern .NET
- Entities
- Entity cache
- Entity loading, cache, and query options
- Detecting entity changes
- Entity change event reference
- Transactions
- Security Center events
- Actions
- Security Desk
- Custom events
- Event-to-actions
- Managing macros
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Entity write restrictions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Ingesting third-party video
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- About incidents
- Maps
- Logging
- Overview
- Localization
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Engine extensions
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- Modern .NET support
- Overview
- Localization
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Custom actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- Media file formats
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Endpoints
- Constants
- Enumerations
- Under the hood
- Troubleshooting