Documentation
¶
Index ¶
- Constants
- Variables
- func GenQueryName() string
- func IsCarveQuery(query string) bool
- func QueryExpiration(exp int) time.Time
- type DistributedQuery
- type DistributedQueryTarget
- type NodeQuery
- type Queries
- func (q *Queries) Activate(name string, envid uint) error
- func (q *Queries) CleanupCompletedQueries(envid uint) error
- func (q *Queries) CleanupExpiredCarves(envid uint) error
- func (q *Queries) CleanupExpiredQueries(envid uint) error
- func (q *Queries) Complete(name string, envid uint) error
- func (q *Queries) Create(query *DistributedQuery) error
- func (q *Queries) CreateNodeQueries(nodeIDs []uint, queryID uint) error
- func (q *Queries) CreateSaved(name, query, creator string, envid uint) error
- func (q *Queries) CreateTarget(name, targetType, targetValue string) error
- func (q *Queries) Delete(name string, envid uint) error
- func (q *Queries) DeleteSaved(name, creator string, envid uint) error
- func (q *Queries) DeleteSavedByEnv(name string, envid uint) error
- func (q *Queries) Exists(name string, envid uint) bool
- func (q *Queries) Expire(name string, envid uint) error
- func (q *Queries) Get(name string, envid uint) (DistributedQuery, error)
- func (q *Queries) GetActive(envid uint) ([]DistributedQuery, error)
- func (q *Queries) GetByEnvTargetPaged(envID uint, target, qtype, search string, page, pageSize int, ...) (QueryListPage, error)
- func (q *Queries) GetCarves(target string, envid uint) ([]DistributedQuery, error)
- func (q *Queries) GetNodeQueryBucketed(nodeID uint, since time.Time, bucketSeconds int) ([]dbutil.BucketedRow, error)
- func (q *Queries) GetNodeQueryTimestamps(nodeID uint, since time.Time) ([]time.Time, error)
- func (q *Queries) GetQueries(target string, envid uint) ([]DistributedQuery, error)
- func (q *Queries) GetSaved(name, creator string, envid uint) (SavedQuery, error)
- func (q *Queries) GetSavedByCreator(creator string, envid uint) ([]SavedQuery, error)
- func (q *Queries) GetSavedByEnv(name string, envid uint) (SavedQuery, error)
- func (q *Queries) GetSavedByEnvPaged(envid uint, search string, page, pageSize int, sortColumn string, desc bool) (SavedQueryListPage, error)
- func (q *Queries) GetTargets(name string) ([]DistributedQueryTarget, error)
- func (q *Queries) Gets(target, qtype string, envid uint) ([]DistributedQuery, error)
- func (q *Queries) IncError(name string, envid uint) error
- func (q *Queries) IncExecution(name string, envid uint) error
- func (q *Queries) NodeQueries(node nodes.OsqueryNode) (QueryReadQueries, bool, error)
- func (q *Queries) SavedExists(name string, envid uint) bool
- func (q *Queries) SetExpected(name string, expected int, envid uint) error
- func (q *Queries) SetNodeQueriesAsExpired(queryID uint) error
- func (q *Queries) UpdateQueryStatus(queryName string, nodeID uint, statusCode int) error
- func (q *Queries) UpdateSaved(name, query string, envid uint) error
- type QueryListPage
- type QueryReadQueries
- type QuerySample
- type QuerySampleCategory
- type QuerySamplePlatform
- type SavedQuery
- type SavedQueryListPage
Constants ¶
const ( // QueryTargetPlatform defines platform as target QueryTargetPlatform string = "platform" // QueryTargetLocalname defines localname as target QueryTargetLocalname string = "localname" // QueryTargetEnvironment defines environment as target QueryTargetEnvironment string = "environment" // QueryTargetUUID defines uuid as target QueryTargetUUID string = "uuid" // StandardQueryType defines a regular query StandardQueryType string = "query" // CarveQueryType defines a regular query CarveQueryType string = "carve" // MetadataQueryType defines a regular query MetadataQueryType string = "metadata" )
const ( // StatusActive defines active status constant StatusActive string = "ACTIVE" // StatusComplete defines complete status constant StatusComplete string = "COMPLETE" // StatusExpired defines expired status constant StatusExpired string = "EXPIRED" )
const ( // TargetAll for all queries but hidden TargetAll string = "all" // TargetAllFull for all queries including hidden ones TargetAllFull string = "all-full" // TargetActive for active queries TargetActive string = "active" // TargetHiddenActive for hidden active queries TargetHiddenActive string = "hidden-active" // TargetCompleted for completed queries TargetCompleted string = "completed" // TargetExpired for expired queries TargetExpired string = "expired" // TargetSaved for saved queries TargetSaved string = "saved" // TargetHiddenCompleted for hidden completed queries TargetHiddenCompleted string = "hidden-completed" // TargetDeleted for deleted queries TargetDeleted string = "deleted" // TargetHidden for hidden queries TargetHidden string = "hidden" )
const ( DistributedQueryStatusPending string = "pending" DistributedQueryStatusCompleted string = "completed" DistributedQueryStatusError string = "error" DistributedQueryStatusExpired string = "expired" )
Variables ¶
var ErrSavedQueryExists = errors.New("saved query already exists")
ErrSavedQueryExists is returned by CreateSaved when the underlying unique index on (name, environment_id) rejects the insert because a row with the same key already exists. Callers should map this to a 409 Conflict response.
var QuerySamples = []QuerySample{ { Name: "host_overview", Description: "Hostname, platform, OS version, kernel — basic host identity.", SQL: "SELECT hostname, computer_name, cpu_brand, physical_memory FROM system_info", Category: CategoryRecon, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "os_version", Description: "Operating system name, version, codename, and build identifiers.", SQL: "SELECT name, version, codename, major, minor, patch, platform, platform_like FROM os_version", Category: CategoryRecon, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "kernel_info", Description: "Running kernel name and version.", SQL: "SELECT name, version FROM kernel_info", Category: CategoryRecon, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin}, }, { Name: "uptime", Description: "How long the host has been up — in days, hours, minutes.", SQL: "SELECT days, hours, minutes, seconds FROM uptime", Category: CategoryRecon, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "running_processes", Description: "All running processes — pid, name, full path, parent pid.", SQL: "SELECT pid, name, path, parent FROM processes", Category: CategoryProcesses, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "processes_root", Description: "Processes running as root / SYSTEM. Quick way to spot abnormal privileged execution.", SQL: "SELECT pid, name, path, uid, cmdline FROM processes WHERE uid = 0", Category: CategoryProcesses, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin}, }, { Name: "processes_no_disk", Description: "Running processes whose executable on disk is missing — classic injected/memory-only indicator.", SQL: "SELECT pid, name, path FROM processes WHERE on_disk = 0", Category: CategoryProcesses, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "local_users", Description: "All local user accounts — username, uid, gid, home directory, shell.", SQL: "SELECT username, uid, gid, directory, shell FROM users", Category: CategoryUsers, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "logged_in_users", Description: "Currently logged-in users with login time and remote host.", SQL: "SELECT user, host, time, tty, type FROM logged_in_users", Category: CategoryUsers, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "sudoers_groups", Description: "Group memberships — useful for spotting unexpected sudo / wheel / admin members.", SQL: "SELECT username, groupname FROM users JOIN user_groups USING(uid) JOIN groups USING(gid)", Category: CategoryUsers, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin}, }, { Name: "listening_ports", Description: "TCP/UDP listeners with the binding process and PID.", SQL: "SELECT pid, port, protocol, address, p.name AS process FROM listening_ports l JOIN processes p USING(pid)", Category: CategoryNetwork, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "active_connections", Description: "Established outbound TCP connections — remote IP and port.", SQL: "SELECT pid, local_address, local_port, remote_address, remote_port FROM process_open_sockets WHERE state = 'ESTABLISHED'", Category: CategoryNetwork, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "arp_cache", Description: "ARP cache entries — recently-seen MAC↔IP pairs on the LAN.", SQL: "SELECT address, mac, interface FROM arp_cache", Category: CategoryNetwork, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "interface_addresses", Description: "All network-interface addresses with subnet masks and broadcast addresses.", SQL: "SELECT interface, address, mask, broadcast FROM interface_addresses", Category: CategoryNetwork, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "crontab_all", Description: "Every cron job on the host across system and per-user crontabs.", SQL: "SELECT command, path, minute, hour, day_of_month, month, day_of_week FROM crontab", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin}, }, { Name: "systemd_units", Description: "Loaded systemd units — name, state, file path. Look for unfamiliar service files.", SQL: "SELECT id, fragment_path, active_state, sub_state, unit_file_state FROM systemd_units", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformLinux}, }, { Name: "launchd_overview", Description: "macOS launchd jobs — daemons and agents loaded at boot/login.", SQL: "SELECT name, path, program, run_at_load, keep_alive, disabled FROM launchd", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformDarwin}, }, { Name: "startup_items", Description: "Windows autostart entries — Run/RunOnce registry keys and Startup folders.", SQL: "SELECT name, path, source, status, type FROM startup_items", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformWindows}, }, { Name: "scheduled_tasks_windows", Description: "Windows Task Scheduler jobs — name, action, last_run_time, enabled state.", SQL: "SELECT name, action, path, enabled, last_run_time, next_run_time FROM scheduled_tasks", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformWindows}, }, { Name: "services_windows", Description: "Windows services — name, display_name, start_type, status, path on disk.", SQL: "SELECT name, display_name, status, start_type, path FROM services", Category: CategoryPersistence, Platforms: []QuerySamplePlatform{PlatformWindows}, }, { Name: "etc_passwd", Description: "Hash, size, owner, permissions of /etc/passwd — classic file-integrity check.", SQL: "SELECT path, size, mode, uid, gid, mtime, sha256 FROM file WHERE path = '/etc/passwd'", Category: CategoryFileIntegrity, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin}, }, { Name: "etc_hosts_contents", Description: "Lines of /etc/hosts — quick way to spot tampering or DNS-override mischief.", SQL: "SELECT address, hostnames FROM etc_hosts", Category: CategoryFileIntegrity, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "windows_hosts_file", Description: "Hash and metadata of the Windows hosts file — should rarely change in a managed fleet.", SQL: "SELECT path, size, mtime, sha256 FROM file WHERE path = 'C:\\Windows\\System32\\drivers\\etc\\hosts'", Category: CategoryFileIntegrity, Platforms: []QuerySamplePlatform{PlatformWindows}, }, { Name: "certificates_trusted", Description: "Trusted certificates in the system store — recent additions can indicate MITM CA installs.", SQL: "SELECT common_name, subject, issuer, not_valid_after, sha1 FROM certificates", Category: CategoryFileIntegrity, Platforms: []QuerySamplePlatform{PlatformLinux, PlatformDarwin, PlatformWindows}, }, { Name: "installed_packages_deb", Description: "Debian / Ubuntu installed packages with version.", SQL: "SELECT name, version, arch FROM deb_packages", Category: CategoryPackages, Platforms: []QuerySamplePlatform{PlatformLinux}, }, { Name: "installed_packages_rpm", Description: "RHEL / Fedora / CentOS installed RPM packages with version.", SQL: "SELECT name, version, arch FROM rpm_packages", Category: CategoryPackages, Platforms: []QuerySamplePlatform{PlatformLinux}, }, { Name: "installed_apps_macos", Description: "macOS .app bundles in /Applications — name, version, bundle id.", SQL: "SELECT name, bundle_identifier, bundle_short_version FROM apps", Category: CategoryPackages, Platforms: []QuerySamplePlatform{PlatformDarwin}, }, { Name: "installed_programs_windows", Description: "Windows installed programs — name, version, publisher, install_date.", SQL: "SELECT name, version, publisher, install_date FROM programs", Category: CategoryPackages, Platforms: []QuerySamplePlatform{PlatformWindows}, }, }
QuerySamples is the canonical starter library. ~20 entries spanning the categories above. Operators are expected to read, clone, and adapt these — they are intentionally simple and SELECT-only.
Ordering matters: this is the order the SPA template row renders, so the most-commonly-useful samples sit first.
var QuerySortableColumns = map[string]string{
"name": "name",
"creator": "creator",
"created": "created_at",
"type": "type",
"expected": "expected",
"executions": "executions",
"errors": "errors",
}
QuerySortableColumns is the closed set of columns external callers may sort by. Enforced in GetByEnvTargetPaged. Mirrors the SortableColumns convention from pkg/nodes.
var SavedQuerySortableColumns = map[string]string{
"name": "name",
"creator": "creator",
"created": "created_at",
"updated": "updated_at",
}
SavedQuerySortableColumns is the closed set of columns external callers may sort by. Enforced in GetSavedByEnvPaged. Mirrors QuerySortableColumns.
Functions ¶
func IsCarveQuery ¶ added in v0.5.2
Helper to check if query is carve
func QueryExpiration ¶
Helper to generate the time.Time for the expiration of a query or carve based on hours
Types ¶
type DistributedQuery ¶
type DistributedQuery struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Name string `gorm:"not null;unique;index" json:"name"`
Creator string `json:"creator"`
Query string `json:"query"`
Expected int `json:"expected"`
Executions int `json:"executions"`
Errors int `json:"errors"`
Active bool `json:"active"`
Hidden bool `json:"hidden"`
Protected bool `json:"protected"`
Completed bool `json:"completed"`
Deleted bool `json:"deleted"`
Expired bool `json:"expired"`
Type string `json:"type"`
Path string `json:"path"`
EnvironmentID uint `json:"environment_id"`
ExtraData string `json:"extra_data"`
Expiration time.Time `json:"expiration"`
Target string `json:"target"`
}
DistributedQuery as abstraction of a distributed query.
Explicit JSON tags (rather than relying on Go's default-PascalCase behavior or an external view projection) so /api/v1/queries and /api/v1/carves responses match the SPA's snake_case contract directly. Fields here are equivalent to embedding gorm.Model — same schema and soft-delete semantics — just with field-level json tags.
type DistributedQueryTarget ¶
type DistributedQueryTarget struct {
gorm.Model
Name string `gorm:"index"`
Type string
Value string
}
DistributedQueryTarget to keep target logic for queries
type NodeQuery ¶
type NodeQuery struct {
gorm.Model
NodeID uint `gorm:"not null;index"`
QueryID uint `gorm:"not null;index"`
Status string `gorm:"type:varchar(10);default:'pending'"`
}
NodeQuery links a node to a query
type Queries ¶
Queries to handle on-demand queries
func CreateQueries ¶
CreateQueries to initialize the queries struct
func (*Queries) CleanupCompletedQueries ¶
CleanupCompletedQueries to set all completed queries as inactive by environment
func (*Queries) CleanupExpiredCarves ¶
CleanupExpiredCarves to set all expired carves as inactive by environment
func (*Queries) CleanupExpiredQueries ¶
CleanupExpiredQueries to set all expired queries as inactive by environment
func (*Queries) Create ¶
func (q *Queries) Create(query *DistributedQuery) error
Create to create new query to be served to nodes
func (*Queries) CreateNodeQueries ¶
CreateNodeQueries to link multiple nodes to a query
func (*Queries) CreateSaved ¶
CreateSaved persists a new saved query. Returns ErrSavedQueryExists when a row with the same (name, env) already exists — the DB unique index `idx_saved_query_name_env` is the authoritative gate, so the handler does not need to win the SavedExists race anymore.
func (*Queries) CreateTarget ¶
CreateTarget to create target entry for a given query
func (*Queries) DeleteSaved ¶
DeleteSaved removes a saved query owned by (creator, env, name). Retained for backward compatibility with non-API callers.
func (*Queries) DeleteSavedByEnv ¶ added in v0.5.2
DeleteSavedByEnv removes a saved query by name within an environment. Returns gorm.ErrRecordNotFound when nothing matched.
func (*Queries) Get ¶
func (q *Queries) Get(name string, envid uint) (DistributedQuery, error)
Get to get a query by name
func (*Queries) GetActive ¶
func (q *Queries) GetActive(envid uint) ([]DistributedQuery, error)
GetActive all active queries and carves by target
func (*Queries) GetByEnvTargetPaged ¶ added in v0.5.2
func (q *Queries) GetByEnvTargetPaged(envID uint, target, qtype, search string, page, pageSize int, sortColumn string, desc bool) (QueryListPage, error)
GetByEnvTargetPaged returns a page of queries for an env + target, with optional free-text search on name/creator/query, optional sort, and canonical pagination. qtype: StandardQueryType or CarveQueryType.
page is 1-indexed. pageSize is clamped to [1, 500] with default 50.
func (*Queries) GetCarves ¶
func (q *Queries) GetCarves(target string, envid uint) ([]DistributedQuery, error)
GetCarves all carve queries by target (active/completed/all/all-full/deleted/hidden)
func (*Queries) GetNodeQueryBucketed ¶ added in v0.5.2
func (q *Queries) GetNodeQueryBucketed(nodeID uint, since time.Time, bucketSeconds int) ([]dbutil.BucketedRow, error)
GetNodeQueryBucketed returns per-bucket row counts for node_queries targeting `nodeID`, since `since`. Same bucketing semantics as the logging-package variants — see pkg/dbutil.BucketExpr for the dialect branching.
func (*Queries) GetNodeQueryTimestamps ¶ added in v0.5.2
GetNodeQueryTimestamps returns just the CreatedAt of every node_query row where this node was the target, since the cutoff. Used by the per-node activity heatmap.
Pluck-style — drags only one column across the wire so the heatmap stays cheap when nodes have many tens of thousands of distributed queries.
func (*Queries) GetQueries ¶
func (q *Queries) GetQueries(target string, envid uint) ([]DistributedQuery, error)
GetQueries all queries by target (active/completed/all/all-full/deleted/hidden)
func (*Queries) GetSaved ¶
func (q *Queries) GetSaved(name, creator string, envid uint) (SavedQuery, error)
GetSaved to get a saved query by name + creator within an environment. Returns gorm.ErrRecordNotFound when no matching row exists — callers can use errors.Is(err, gorm.ErrRecordNotFound) to detect that case.
func (*Queries) GetSavedByCreator ¶
func (q *Queries) GetSavedByCreator(creator string, envid uint) ([]SavedQuery, error)
GetSavedByCreator to get a saved query by creator
func (*Queries) GetSavedByEnv ¶ added in v0.5.2
func (q *Queries) GetSavedByEnv(name string, envid uint) (SavedQuery, error)
GetSavedByEnv returns a saved query by name within an environment without scoping by creator — used by env admins who can manage any saved query. Returns gorm.ErrRecordNotFound when no matching row exists.
func (*Queries) GetSavedByEnvPaged ¶ added in v0.5.2
func (q *Queries) GetSavedByEnvPaged(envid uint, search string, page, pageSize int, sortColumn string, desc bool) (SavedQueryListPage, error)
GetSavedByEnvPaged returns a page of saved queries for an env, with optional free-text search and an allowlisted sort column. pageSize is clamped to [1, 500]; pageSize <= 0 defaults to 50. page is 1-indexed.
func (*Queries) GetTargets ¶
func (q *Queries) GetTargets(name string) ([]DistributedQueryTarget, error)
GetTargets to retrieve targets for a given query
func (*Queries) Gets ¶
func (q *Queries) Gets(target, qtype string, envid uint) ([]DistributedQuery, error)
Gets all queries by target (active/completed/all/all-full/deleted/hidden/expired)
func (*Queries) IncExecution ¶
IncExecution to increase the execution count for this query
func (*Queries) NodeQueries ¶
func (q *Queries) NodeQueries(node nodes.OsqueryNode) (QueryReadQueries, bool, error)
func (*Queries) SavedExists ¶ added in v0.5.2
SavedExists reports whether a saved query with the given name exists in the environment, irrespective of creator.
func (*Queries) SetExpected ¶
SetExpected to set the number of expected executions for this query
func (*Queries) SetNodeQueriesAsExpired ¶ added in v0.4.4
SetNodeQueriesAsExpired marks all pending node queries for a specific distributed query as expired
func (*Queries) UpdateQueryStatus ¶
UpdateQueryStatus to update the status of each query
func (*Queries) UpdateSaved ¶
UpdateSaved updates the SQL body of an existing saved query identified by (name, env). The creator field is not modified — original ownership stays. Returns gorm.ErrRecordNotFound when the row does not exist.
type QueryListPage ¶ added in v0.5.2
type QueryListPage struct {
Items []DistributedQuery
TotalItems int64
}
QueryListPage is the canonical paginated-list result for queries.
type QueryReadQueries ¶
QueryReadQueries to hold all the on-demand queries
type QuerySample ¶ added in v0.5.2
type QuerySample struct {
Name string `json:"name"`
Description string `json:"description"`
SQL string `json:"sql"`
Category QuerySampleCategory `json:"category"`
Platforms []QuerySamplePlatform `json:"platforms"`
}
QuerySample is one starter sample row.
type QuerySampleCategory ¶ added in v0.5.2
type QuerySampleCategory string
QuerySampleCategory is the closed set of category tags. Surfaced in the SPA so templates can group; kept as a typed string so a typo at sample-add time becomes a compile error.
const ( CategoryRecon QuerySampleCategory = "recon" CategoryProcesses QuerySampleCategory = "processes" CategoryUsers QuerySampleCategory = "users" CategoryNetwork QuerySampleCategory = "network" CategoryPersistence QuerySampleCategory = "persistence" CategoryFileIntegrity QuerySampleCategory = "file_integrity" CategoryPackages QuerySampleCategory = "packages" )
type QuerySamplePlatform ¶ added in v0.5.2
type QuerySamplePlatform string
QuerySamplePlatform — a platform tag a sample claims to support. Aligns with pkg/nodes platform buckets (linux / darwin / windows). A sample applicable to every platform tagged with `linux, darwin, windows`.
const ( PlatformLinux QuerySamplePlatform = "linux" PlatformDarwin QuerySamplePlatform = "darwin" PlatformWindows QuerySamplePlatform = "windows" )
type SavedQuery ¶
type SavedQuery struct {
gorm.Model
Name string `gorm:"uniqueIndex:idx_saved_query_name_env"`
Creator string
Query string
EnvironmentID uint `gorm:"uniqueIndex:idx_saved_query_name_env"`
ExtraData string
}
SavedQuery as abstraction of a saved query to be used in distributed, schedule or packs.
Composite unique index on (name, environment_id) — gorm AutoMigrate emits it as `idx_saved_query_name_env`. This is the structural fix for the TOCTOU race in SavedQueryCreateHandler: a concurrent pair of POSTs with the same name + env both pass the SavedExists precheck, both attempt CreateSaved; with the unique index, the second Create returns a duplicate-key error and the handler can map it to 409 cleanly.
type SavedQueryListPage ¶ added in v0.5.2
type SavedQueryListPage struct {
Items []SavedQuery
TotalItems int64
}
SavedQueryListPage is the canonical paginated-list result for saved queries.