Skip to main content

Table

Tables are the primary way to explore view data in Mission Control. They render rows from your queries, apply mappings, and drive filters, cards, and links into configs or other views.

When to Use

  • You need sortable, filterable rows for configs, changes, or metrics.
  • You want to mix multiple data sources into one grid via SQL merge.
  • You want card layouts (titles, subtitles, gauges) backed by the same table data.

Anatomy of a Table View

  • columns: Define the schema and rendering (type, filters, card placement, URLs).
  • queries: Pull data from configs, changes, Prometheus, SQL, or other views.
  • merge: Optional SQL to join multiple queries into one result set.
  • mapping: Optional CEL to reshape query fields into your columns.
  • templating: Variables used inside queries and filters for user-scoped data.

Example: Namespace Table

This view shows how card positioning, gauges, and filters are driven by columns:

namespace.yaml
apiVersion: mission-control.flanksource.com/v1
kind: View
metadata:
name: namespace
namespace: mc
spec:
columns:
- cardPosition: title
name: name
primaryKey: true
type: config_item
url:
config: row.id
- cardPosition: deck
filter:
type: multiselect
icon: row.health
name: status
type: status
- filter:
type: multiselect
hidden: true
name: health
type: health
- cardPosition: deck
description: Memory usage
gauge:
max: row.memory_limit
thresholds:
- color: green
percent: 0
- color: orange
percent: 75
- color: red
percent: 90
name: memory
type: gauge
unit: bytes
- cardPosition: deck
gauge:
max: row.cpu_limit
thresholds:
- color: "#8BC34A"
percent: 0
- color: "#F4B23C"
percent: 70
- color: "#F25C54"
percent: 85
name: cpu
type: gauge
unit: millicore
- cardPosition: footer
name: updated
type: datetime
- cardPosition: footer
name: created
type: datetime
- cardPosition: body
hidden: true
name: image
type: string
- cardPosition: body
hidden: true
name: node
type: string
description: View for inspecting a Kubernetes Namespace
display:
icon: namespace
plugins:
- configTab:
types:
- Kubernetes::Namespace
variables:
cluster: $(.config.tags.cluster)
namespace: $(.config.name)
sidebar: true
title: Namespace
mapping:
created: row.created_at
image: |
has(row.config) ? row.config.JSON().spec.containers[0].image : "N/A"
node: |
has(row.config) ? (has(row.config.JSON().spec.nodeName) ? row.config.JSON().spec.nodeName : "N/A"): "N/A"
updated: row.updated_at
merge: |
"SELECT
pod.id,
pod.name,
json_extract(pod.tags, '$.namespace')
AS namespace,
pod.status,
pod.health,
pod.config,
pod.created_at,
pod.updated_at,
memory.value as memory,
to_bytes(COALESCE(
json_extract(pod.config,
'$.spec.containers[0].resources.limits.memory'),
''
)) AS memory_limit,
cpu.value as cpu,
to_millicores(COALESCE(
json_extract(pod.config, '$.spec.containers[0].resources.limits.cpu'),

''
)) AS cpu_limit\nFROM pod\nLEFT JOIN memory
ON pod.name = memory.pod

AND json_extract(pod.tags, '$.namespace') = memory.namespace\nLEFT JOIN cpu
ON pod.name = cpu.pod
AND json_extract(pod.tags, '$.namespace') = cpu.namespace\nORDER
BY namespace, name
"
panels:
- description: Total Pods in the namespace
name: Total Pods
query: SELECT COUNT(*) AS value FROM pod
type: number
queries:
cpu:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum by (namespace, pod) (
irate(container_cpu_usage_seconds_total{
container!="POD", # Skip The pause/infra container
image!="" # Skip dead containers
}[30s])
) * 1000
tls:
ca: {}
cert: {}
key: {}
username: {}
memory:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum by (namespace, pod) (
container_memory_working_set_bytes{
container!="POD", # Skip The pause/infra container
image!="" # Skip dead containers
}
)
tls:
ca: {}
cert: {}
key: {}
username: {}
memory_usage:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum(rate(container_memory_working_set_bytes{namespace=~"$(.var.namespace)", image!=""}[5m])) / sum(machine_memory_bytes{})
tls:
ca: {}
cert: {}
key: {}
username: {}
pod:
configs:
agent: all
tagSelector: cluster=$(.var.cluster),namespace=$(.var.namespace)
types:
- Kubernetes::Pod
templating:
- key: cluster
label: Cluster
valueFrom:
config:
types:
- Kubernetes::Cluster
- dependsOn:
- cluster
key: namespace
label: Namespace
valueFrom:
config:
tagSelector: cluster=$(.var.cluster)
types:
- Kubernetes::Namespace
status: {}

Joining Multiple Data Sources

Use merge to combine configs with Prometheus metrics into one table:

namespace.yaml
apiVersion: mission-control.flanksource.com/v1
kind: View
metadata:
name: namespace
namespace: mc
spec:
columns:
- cardPosition: title
name: name
primaryKey: true
type: config_item
url:
config: row.id
- cardPosition: deck
filter:
type: multiselect
icon: row.health
name: status
type: status
- filter:
type: multiselect
hidden: true
name: health
type: health
- cardPosition: deck
description: Memory usage
gauge:
max: row.memory_limit
thresholds:
- color: green
percent: 0
- color: orange
percent: 75
- color: red
percent: 90
name: memory
type: gauge
unit: bytes
- cardPosition: deck
gauge:
max: row.cpu_limit
thresholds:
- color: "#8BC34A"
percent: 0
- color: "#F4B23C"
percent: 70
- color: "#F25C54"
percent: 85
name: cpu
type: gauge
unit: millicore
- cardPosition: footer
name: updated
type: datetime
- cardPosition: footer
name: created
type: datetime
- cardPosition: body
hidden: true
name: image
type: string
- cardPosition: body
hidden: true
name: node
type: string
description: View for inspecting a Kubernetes Namespace
display:
icon: namespace
plugins:
- configTab:
types:
- Kubernetes::Namespace
variables:
cluster: $(.config.tags.cluster)
namespace: $(.config.name)
sidebar: true
title: Namespace
mapping:
created: row.created_at
image: |
has(row.config) ? row.config.JSON().spec.containers[0].image : "N/A"
node: |
has(row.config) ? (has(row.config.JSON().spec.nodeName) ? row.config.JSON().spec.nodeName : "N/A"): "N/A"
updated: row.updated_at
merge: |
"SELECT
pod.id,
pod.name,
json_extract(pod.tags, '$.namespace')
AS namespace,
pod.status,
pod.health,
pod.config,
pod.created_at,
pod.updated_at,
memory.value as memory,
to_bytes(COALESCE(
json_extract(pod.config,
'$.spec.containers[0].resources.limits.memory'),
''
)) AS memory_limit,
cpu.value as cpu,
to_millicores(COALESCE(
json_extract(pod.config, '$.spec.containers[0].resources.limits.cpu'),

''
)) AS cpu_limit\nFROM pod\nLEFT JOIN memory
ON pod.name = memory.pod

AND json_extract(pod.tags, '$.namespace') = memory.namespace\nLEFT JOIN cpu
ON pod.name = cpu.pod
AND json_extract(pod.tags, '$.namespace') = cpu.namespace\nORDER
BY namespace, name
"
panels:
- description: Total Pods in the namespace
name: Total Pods
query: SELECT COUNT(*) AS value FROM pod
type: number
queries:
cpu:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum by (namespace, pod) (
irate(container_cpu_usage_seconds_total{
container!="POD", # Skip The pause/infra container
image!="" # Skip dead containers
}[30s])
) * 1000
tls:
ca: {}
cert: {}
key: {}
username: {}
memory:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum by (namespace, pod) (
container_memory_working_set_bytes{
container!="POD", # Skip The pause/infra container
image!="" # Skip dead containers
}
)
tls:
ca: {}
cert: {}
key: {}
username: {}
memory_usage:
columns:
namespace: string
pod: string
value: decimal
prometheus:
bearer: {}
connection: connection://mc/prometheus
oauth:
clientID: {}
clientSecret: {}
password: {}
query: |
sum(rate(container_memory_working_set_bytes{namespace=~"$(.var.namespace)", image!=""}[5m])) / sum(machine_memory_bytes{})
tls:
ca: {}
cert: {}
key: {}
username: {}
pod:
configs:
agent: all
tagSelector: cluster=$(.var.cluster),namespace=$(.var.namespace)
types:
- Kubernetes::Pod
templating:
- key: cluster
label: Cluster
valueFrom:
config:
types:
- Kubernetes::Cluster
- dependsOn:
- cluster
key: namespace
label: Namespace
valueFrom:
config:
tagSelector: cluster=$(.var.cluster)
types:
- Kubernetes::Namespace
status: {}

Best Practices

  • Always set primaryKey on at least one column (composite keys allowed).
  • Prefer mapping to normalize field names and units (e.g., bytes, millicores).
  • Keep merge focused on shaping/joins; leave presentation to columns.
  • Enable filter.type: multiselect on columns users need to slice without refresh.
  • Use templated variables for scoping (e.g., cluster/namespace) instead of hard-coding tags.