UIPackage
Menu

Framework

Change language

Boilerplate repo

Data Mesh Domain Catalog

blockdevops

Zhamak Dehghani Data Mesh architecture domain catalog, Data Products directory, polyglot output ports (Snowflake, Kafka, GraphQL, S3), and federated computational governance SLA scorecards.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/data-mesh-domain-catalog.json
Named registry:npx shadcn-vue@latest add @uipkge/data-mesh-domain-catalogInstalls to:app/components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
classHTMLAttributes['class']optional
initialSelectedProductIdstring'dp_customer_360_profile'optional
initialDomainFilterstring'all'optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

DomainMeta
interface DomainMeta {
  id: string
  name: string
  lead: string
  role: string
  productsCount: number
  slaScore: number
  activeConsumers: number
  storageVolume: string
  throughput: string
  tier: string
  description: string
  iconName: 'CreditCard' | 'Users' | 'Truck' | 'Activity'
}
OutputPortSchemaColumn
interface OutputPortSchemaColumn {
  name: string
  type: string
  nullable: boolean
  isKey?: boolean
  desc: string
  constraints?: string
}
OutputPortDetails
interface OutputPortDetails {
  id: DataMeshPortId
  name: string
  typeLabel: string
  badgeVariant?: 'default' | 'secondary' | 'outline'
  protocol: string
  uri: string
  accessRole: string
  schemaRegistry?: string
  format?: string
  syncCadence?: string
  throughput?: string
  rateLimit?: string
  columns?: OutputPortSchemaColumn[]
  sampleQuery?: string
  samplePayload?: string
  mockResults?: Array<Record<string, string | number | boolean>>
}
DataProductItem
interface DataProductItem {
  id: string
  name: string
  displayName: string
  domainId: string
  domainName: string
  version: string
  tier: string
  classification: 'PII-Confidential' | 'Financial-Audit' | 'Internal-Ops' | 'Platform-Internal'
  description: string
  contractHash: string
  owner: {
    name: string
    role: string
    handle: string
    team: string
    initials: string
    avatar?: string
  }
  slaScore: number
  freshnessLatency: string
  uptime: string
  assertionsPassed: number
  assertionsTotal: number
  activeConsumersCount: number
  topConsumers: string[]
  ports: OutputPortDetails[]
  governancePolicies: string[]
}

Files installed (6)

  • app/components/blocks/DataMeshDomainCatalog.vue21.2 kB
    <script setup lang="ts">
    import { computed, ref } from 'vue'
    import type { HTMLAttributes } from 'vue'
    import {
      Activity,
      Copy,
      CreditCard,
      Database,
      Eye,
      FileCode2,
      HardDrive,
      KeyRound,
      Layers,
      MoreHorizontal,
      Network,
      Plus,
      Search,
      ShieldCheck,
      Table2,
      Terminal,
      Truck,
      Users,
      Zap,
    } from 'lucide-vue-next'
    import { cn } from '@/lib/utils'
    import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import {
      DropdownMenu,
      DropdownMenuContent,
      DropdownMenuItem,
      DropdownMenuLabel,
      DropdownMenuSeparator,
      DropdownMenuTrigger,
    } from '@/components/ui/dropdown-menu'
    import { Progress } from '@/components/ui/progress'
    import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
    import DataMeshProductDetail from './DataMeshProductDetail.vue'
    import DataMeshQueryRunner from './DataMeshQueryRunner.vue'
    import DataMeshModals from './DataMeshModals.vue'
    import { DOMAINS, DATA_PRODUCTS } from './data-mesh-data'
    import type {
      DomainMeta,
      OutputPortSchemaColumn,
      OutputPortDetails,
      DataProductItem,
      DataMeshPortId,
    } from './data-mesh-types'
    
    export type { DomainMeta, OutputPortSchemaColumn, OutputPortDetails, DataProductItem, DataMeshPortId }
    
    const props = withDefaults(
      defineProps<{
        class?: HTMLAttributes['class']
        initialSelectedProductId?: string
        initialDomainFilter?: string
      }>(),
      {
        initialSelectedProductId: 'dp_customer_360_profile',
        initialDomainFilter: 'all',
      },
    )
    
    const domains = DOMAINS
    const dataProducts = DATA_PRODUCTS
    
    const selectedDomainFilter = ref<string>(props.initialDomainFilter)
    const selectedPortTypeFilter = ref<string>('all')
    const searchQuery = ref('')
    const selectedProductId = ref<string>(props.initialSelectedProductId)
    const activePortId = ref<DataMeshPortId>('snowflake')
    const copiedUri = ref<string | null>(null)
    
    // Modal states
    const isAccessModalOpen = ref(false)
    const isRegisterModalOpen = ref(false)
    const isQueryDrawerOpen = ref(false)
    const isSimulatingQuery = ref(false)
    
    const selectedProduct = computed(() => {
      return dataProducts.find((p) => p.id === selectedProductId.value) || dataProducts[0]
    })
    
    const activePort = computed(() => {
      const currentPorts = selectedProduct.value.ports
      return currentPorts.find((p) => p.id === activePortId.value) || currentPorts[0]
    })
    
    const filteredDataProducts = computed(() => {
      return dataProducts.filter((product) => {
        if (selectedDomainFilter.value !== 'all' && product.domainId !== selectedDomainFilter.value) {
          return false
        }
        if (selectedPortTypeFilter.value !== 'all') {
          const hasPort = product.ports.some((p) => p.id === selectedPortTypeFilter.value)
          if (!hasPort) return false
        }
        if (searchQuery.value.trim()) {
          const q = searchQuery.value.toLowerCase()
          const matchName = product.name.toLowerCase().includes(q)
          const matchDisplay = product.displayName.toLowerCase().includes(q)
          const matchDomain = product.domainName.toLowerCase().includes(q)
          const matchOwner = product.owner.name.toLowerCase().includes(q)
          const matchDesc = product.description.toLowerCase().includes(q)
          if (!matchName && !matchDisplay && !matchDomain && !matchOwner && !matchDesc) {
            return false
          }
        }
        return true
      })
    })
    
    function selectDomain(domainId: string) {
      if (selectedDomainFilter.value === domainId) {
        selectedDomainFilter.value = 'all'
      } else {
        selectedDomainFilter.value = domainId
      }
    }
    
    function selectProduct(product: DataProductItem) {
      selectedProductId.value = product.id
      if (product.ports.length > 0) {
        activePortId.value = product.ports[0].id
      }
    }
    
    function copyToClipboard(text: string, identifier: string) {
      if (typeof navigator !== 'undefined' && navigator.clipboard) {
        navigator.clipboard.writeText(text)
        copiedUri.value = identifier
        setTimeout(() => {
          copiedUri.value = null
        }, 2000)
      }
    }
    
    function runSimulatedQuery() {
      isSimulatingQuery.value = true
      setTimeout(() => {
        isSimulatingQuery.value = false
      }, 1000)
    }
    
    function handleOpenAccessModal(product: DataProductItem) {
      selectProduct(product)
      isAccessModalOpen.value = true
    }
    
    function handleOpenQueryRunner(product: DataProductItem) {
      selectProduct(product)
      isQueryDrawerOpen.value = true
    }
    </script>
    
    <template>
      <div
        data-slot="data-mesh-domain-catalog"
        :class="
          cn(
            'bg-background text-foreground border-border w-full space-y-6 rounded-xl border p-4 shadow-xs sm:p-6',
            props.class,
          )
        "
      >
        <!-- Header Section -->
        <header class="border-border bg-card/70 rounded-xl border p-4 sm:p-5">
          <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
            <div class="space-y-2">
              <!-- Top Row: Org Breadcrumb & Governance Status -->
              <div class="flex flex-wrap items-center gap-2 text-xs">
                <div class="bg-primary/10 text-primary flex size-6 items-center justify-center rounded-md">
                  <Network class="size-3.5" />
                </div>
                <span class="text-foreground font-semibold">UIPKGE Global Data Mesh</span>
                <span class="text-muted-foreground font-mono">4 Core Domains</span>
                <div
                  class="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 font-medium"
                >
                  <span class="relative flex size-2">
                    <span class="bg-success absolute inline-flex size-full rounded-full opacity-75" />
                    <span class="bg-success relative inline-flex size-2 rounded-full" />
                  </span>
                  <ShieldCheck class="text-success size-3.5" />
                  <span>Federated Computational Governance Active</span>
                </div>
              </div>
    
              <!-- Main Title -->
              <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                Data Mesh Domain Catalog & Data Products Directory
              </h1>
              <p class="text-muted-foreground max-w-3xl text-xs leading-relaxed sm:text-sm">
                Zhamak Dehghani architectural architecture for decentralized domain data ownership, discoverable polyglot
                output ports, and automated computational governance SLA scorecards.
              </p>
            </div>
    
            <!-- Header Action Button -->
            <div class="flex shrink-0 items-center gap-2 pt-1 lg:pt-0">
              <Button
                variant="default"
                size="sm"
                class="h-9 gap-1.5 text-xs font-medium"
                @click="isRegisterModalOpen = true"
              >
                <Plus class="size-3.5" />
                <span>Register New Data Product</span>
              </Button>
            </div>
          </div>
        </header>
    
        <!-- 4 Domain Summary Cards -->
        <section class="space-y-3">
          <div class="flex items-center justify-between gap-x-2 px-1">
            <div class="flex items-center gap-2">
              <Layers class="text-primary size-4" />
              <h2 class="text-foreground text-xs font-semibold tracking-wider uppercase">Decentralized Domain Nodes (4)</h2>
            </div>
            <span class="text-muted-foreground text-xs">Click a domain card to filter Data Products</span>
          </div>
    
          <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
            <div
              v-for="domain in domains"
              :key="domain.id"
              :class="
                cn(
                  'group relative cursor-pointer space-y-3 rounded-xl border p-4 text-xs shadow-xs transition-colors',
                  selectedDomainFilter === domain.id
                    ? 'border-primary bg-primary/5 ring-primary/30 ring-1'
                    : 'border-border bg-card hover:border-border/80 hover:bg-muted/30',
                )
              "
              @click="selectDomain(domain.id)"
            >
              <!-- Domain Card Header -->
              <div class="flex items-start justify-between gap-2">
                <div class="flex items-center gap-2.5">
                  <div class="bg-muted border-border/80 flex size-8 shrink-0 items-center justify-center rounded-lg border">
                    <CreditCard v-if="domain.iconName === 'CreditCard'" class="text-chart-1 size-4" />
                    <Users v-else-if="domain.iconName === 'Users'" class="text-info size-4" />
                    <Truck v-else-if="domain.iconName === 'Truck'" class="text-warning size-4" />
                    <Activity v-else class="text-success size-4" />
                  </div>
                  <div>
                    <h3 class="text-foreground leading-tight font-semibold">{{ domain.name }}</h3>
                    <span class="text-muted-foreground text-xs">{{ domain.productsCount }} Data Products</span>
                  </div>
                </div>
    
                <Badge
                  :variant="selectedDomainFilter === domain.id ? 'default' : 'secondary'"
                  class="shrink-0 font-mono text-xs"
                >
                  {{ selectedDomainFilter === domain.id ? 'Filtered' : `${domain.slaScore}% SLA` }}
                </Badge>
              </div>
    
              <!-- Domain Description -->
              <p class="text-muted-foreground line-clamp-2 leading-relaxed">
                {{ domain.description }}
              </p>
    
              <!-- Domain Meta & Operational Metrics -->
              <div class="border-border/60 grid grid-cols-2 gap-2 border-t pt-2.5 text-xs">
                <div>
                  <span class="text-muted-foreground block">Storage Vol</span>
                  <span class="text-foreground font-mono font-medium">{{ domain.storageVolume }}</span>
                </div>
                <div>
                  <span class="text-muted-foreground block">Throughput</span>
                  <span class="text-foreground font-mono font-medium">{{ domain.throughput }}</span>
                </div>
              </div>
    
              <!-- Domain Lead Info -->
              <div class="bg-muted/50 flex items-center justify-between gap-x-2 rounded-lg px-2.5 py-1.5">
                <div class="flex items-center gap-1.5 overflow-hidden">
                  <Avatar class="size-4.5 shrink-0">
                    <AvatarFallback class="text-xs">
                      {{
                        domain.lead
                          .split(' ')
                          .map((n) => n[0])
                          .join('')
                      }}
                    </AvatarFallback>
                  </Avatar>
                  <span class="text-foreground truncate font-medium">{{ domain.lead }}</span>
                </div>
                <span class="text-muted-foreground shrink-0 font-mono text-xs">{{ domain.activeConsumers }} clients</span>
              </div>
            </div>
          </div>
        </section>
    
        <!-- Data Products Table Section -->
        <section class="border-border bg-card space-y-4 rounded-xl border p-4 shadow-xs sm:p-5">
          <!-- Toolbar: Title, Search, Port Type Selector -->
          <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
            <div class="space-y-0.5">
              <div class="flex items-center gap-2">
                <Database class="text-primary size-4" />
                <h2 class="text-foreground text-sm font-semibold">Data Products Catalog</h2>
                <Badge variant="outline" class="font-mono text-xs">
                  {{ filteredDataProducts.length }} / {{ dataProducts.length }}
                </Badge>
              </div>
              <p class="text-muted-foreground text-xs">
                Autonomous product quantum nodes adhering to computational policies and explicit input/output schemas.
              </p>
            </div>
    
            <div class="flex flex-wrap items-center gap-2">
              <!-- Port Filter Selector -->
              <div class="flex items-center gap-1.5">
                <span class="text-muted-foreground text-xs font-medium">Output:</span>
                <select
                  v-model="selectedPortTypeFilter"
                  class="border-border bg-background text-foreground focus-visible:ring-ring h-8 rounded-md border px-2.5 text-xs outline-none focus-visible:ring-1"
                >
                  <option value="all">All Ports</option>
                  <option value="snowflake">Snowflake View</option>
                  <option value="kafka">Kafka Stream</option>
                  <option value="graphql">GraphQL Mesh</option>
                  <option value="s3">S3 Delta Lake</option>
                </select>
              </div>
    
              <!-- Search Input -->
              <div class="relative w-full sm:w-56">
                <Search class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2" />
                <input
                  v-model="searchQuery"
                  placeholder="Search product, owner, domain..."
                  class="border-border bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border pr-3 pl-8 text-xs outline-none focus-visible:ring-1"
                />
              </div>
            </div>
          </div>
    
          <!-- Main Data Products Table -->
          <div class="border-border overflow-x-auto rounded-lg border">
            <Table>
              <TableHeader>
                <TableRow class="bg-muted/40">
                  <TableHead class="text-xs">Data Product & Contract</TableHead>
                  <TableHead class="text-xs">Domain Node</TableHead>
                  <TableHead class="text-xs">Freshness SLA</TableHead>
                  <TableHead class="text-xs">Active Output Ports</TableHead>
                  <TableHead class="text-xs">Consumers & Traffic</TableHead>
                  <TableHead class="text-right text-xs">Actions</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                <TableRow
                  v-for="product in filteredDataProducts"
                  :key="product.id"
                  :class="
                    cn(
                      'hover:bg-muted/50 cursor-pointer transition-colors',
                      selectedProductId === product.id && 'bg-primary/5 font-medium',
                    )
                  "
                  @click="selectProduct(product)"
                >
                  <!-- Name & Contract -->
                  <TableCell class="py-3">
                    <div class="space-y-1">
                      <div class="flex items-center gap-2">
                        <span class="text-foreground font-semibold">{{ product.displayName }}</span>
                        <Badge variant="outline" class="font-mono text-xs">v{{ product.version }}</Badge>
                      </div>
                      <div class="flex items-center gap-1.5 text-xs">
                        <code class="text-muted-foreground font-mono">{{ product.name }}</code>
                        <span class="text-muted-foreground/40">·</span>
                        <Badge
                          :variant="
                            product.classification === 'PII-Confidential'
                              ? 'destructive'
                              : product.classification === 'Financial-Audit'
                                ? 'default'
                                : 'secondary'
                          "
                          class="text-xs"
                        >
                          {{ product.classification }}
                        </Badge>
                      </div>
                    </div>
                  </TableCell>
    
                  <!-- Domain Node & Owner -->
                  <TableCell class="py-3">
                    <div class="space-y-1">
                      <span class="text-foreground text-xs font-medium">{{ product.domainName }}</span>
                      <div class="flex items-center gap-1.5">
                        <Avatar class="size-4 shrink-0">
                          <AvatarFallback class="text-xs">{{ product.owner.initials }}</AvatarFallback>
                        </Avatar>
                        <span class="text-muted-foreground text-xs">{{ product.owner.name }}</span>
                      </div>
                    </div>
                  </TableCell>
    
                  <!-- SLA & Quality Telemetry -->
                  <TableCell class="py-3">
                    <div class="w-36 space-y-1.5">
                      <div class="flex items-center justify-between text-xs">
                        <span class="text-muted-foreground font-mono">SLA Score</span>
                        <span class="text-success font-mono font-semibold"> {{ product.slaScore }}% </span>
                      </div>
                      <Progress :model-value="product.slaScore" class="h-1.5" />
                      <div class="text-muted-foreground flex items-center justify-between font-mono text-xs">
                        <span>Latency: {{ product.freshnessLatency }}</span>
                        <span>{{ product.assertionsPassed }}/{{ product.assertionsTotal }} Checks</span>
                      </div>
                    </div>
                  </TableCell>
    
                  <!-- Active Output Ports Badges -->
                  <TableCell class="py-3">
                    <div class="flex flex-wrap gap-1.5">
                      <Badge
                        v-for="port in product.ports"
                        :key="port.id"
                        :variant="port.id === activePortId && selectedProductId === product.id ? 'default' : 'secondary'"
                        class="h-6 gap-1 font-mono text-xs"
                      >
                        <Table2 v-if="port.id === 'snowflake'" class="text-info size-3" />
                        <Zap v-else-if="port.id === 'kafka'" class="text-warning size-3" />
                        <Network v-else-if="port.id === 'graphql'" class="size-3 text-pink-400" />
                        <HardDrive v-else class="text-success size-3" />
                        <span>{{ port.id.toUpperCase() }}</span>
                      </Badge>
                    </div>
                  </TableCell>
    
                  <!-- Consumers & Volume -->
                  <TableCell class="py-3">
                    <div class="space-y-0.5 text-xs">
                      <div class="flex items-center gap-1 text-xs">
                        <Users class="text-muted-foreground size-3" />
                        <span class="text-foreground font-semibold">{{ product.activeConsumersCount }}</span>
                        <span class="text-muted-foreground">registered apps</span>
                      </div>
                      <p class="text-muted-foreground truncate font-mono text-xs">
                        {{ product.topConsumers.slice(0, 2).join(', ') }}
                      </p>
                    </div>
                  </TableCell>
    
                  <!-- Row Action Dropdown -->
                  <TableCell class="py-3 text-right" @click.stop>
                    <DropdownMenu>
                      <DropdownMenuTrigger as-child>
                        <Button variant="ghost" size="sm" class="size-8 p-0">
                          <MoreHorizontal class="size-4" />
                        </Button>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent align="end" class="w-48 text-xs">
                        <DropdownMenuLabel class="text-xs">Product Actions</DropdownMenuLabel>
                        <DropdownMenuItem class="cursor-pointer gap-2" @click="selectProduct(product)">
                          <Eye class="size-3.5" />
                          <span>View Specifications</span>
                        </DropdownMenuItem>
                        <DropdownMenuItem class="cursor-pointer gap-2" @click="handleOpenQueryRunner(product)">
                          <Terminal class="text-primary size-3.5" />
                          <span>Interactive Query Runner</span>
                        </DropdownMenuItem>
                        <DropdownMenuItem class="cursor-pointer gap-2" @click="handleOpenAccessModal(product)">
                          <KeyRound class="text-warning size-3.5" />
                          <span>Request Consumer Access</span>
                        </DropdownMenuItem>
                        <DropdownMenuSeparator />
                        <DropdownMenuItem
                          class="cursor-pointer gap-2"
                          @click="copyToClipboard(product.ports[0]?.uri || '', product.id)"
                        >
                          <Copy class="size-3.5" />
                          <span>Copy Primary Port URI</span>
                        </DropdownMenuItem>
                        <DropdownMenuItem
                          class="cursor-pointer gap-2"
                          @click="copyToClipboard(product.contractHash, product.contractHash)"
                        >
                          <FileCode2 class="text-chart-2 size-3.5" />
                          <span>Copy Contract Hash</span>
                        </DropdownMenuItem>
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </TableCell>
                </TableRow>
              </TableBody>
            </Table>
          </div>
        </section>
    
        <!-- Data Product Output Port Architecture & Governance Card (Selected Product) -->
        <DataMeshProductDetail
          :product="selectedProduct"
          :port="activePort"
          :active-port-id="activePortId"
          :copied-key="copiedUri"
          @update:active-port-id="activePortId = $event"
          @copy="copyToClipboard"
          @request-access="handleOpenAccessModal"
          @run-query="handleOpenQueryRunner"
        />
    
        <!-- Interactive Drawer / Modal: Query Output Port -->
        <DataMeshQueryRunner
          :open="isQueryDrawerOpen"
          :product="selectedProduct"
          :port="activePort"
          :is-simulating-query="isSimulatingQuery"
          @close="isQueryDrawerOpen = false"
          @run-query="runSimulatedQuery"
        />
    
        <!-- Modals: Request Access & Register Product -->
        <DataMeshModals
          v-model:access-modal-open="isAccessModalOpen"
          v-model:register-modal-open="isRegisterModalOpen"
          :product="selectedProduct"
          :domains="domains"
        />
      </div>
    </template>
    
  • app/components/blocks/DataMeshProductDetail.vue15.4 kB
  • app/components/blocks/DataMeshQueryRunner.vue3.1 kB
  • app/components/blocks/DataMeshModals.vue11.4 kB
  • app/components/blocks/data-mesh-types.ts1.6 kB
  • app/components/blocks/data-mesh-data.ts24.7 kB

Raw manifest:https://uipkge.dev/r/vue/data-mesh-domain-catalog.json