Data Model
License Server Detail uses Convex as its real-time database backend. This document describes the complete data model and schema.
Overview
Section titled “Overview”The data model consists of five primary tables:
| Table | Purpose |
|---|---|
servers | License server configurations and metadata |
serverHealthChecks | Historical health check results |
licenseSnapshots | Periodic license utilization snapshots |
licenseSessions | Active license checkout sessions |
alerts | Generated alerts and notifications |
Database Schema
Section titled “Database Schema”Servers Table
Section titled “Servers Table”Stores license server configurations and connection information:
servers: defineTable({ // Basic identification name: v.string(), fqdn: v.string(), ip: v.string(),
// Server details applications: v.array(v.string()), os: v.string(), licenseServerType: v.string(), vlan: v.string(),
// Optional metadata physicalVirtual: v.optional(v.string()), extraInfo: v.optional(v.string()), companyUrl: v.optional(v.string()), renewalDate: v.optional(v.string()), backupDrPlan: v.optional(v.string()),
// Connection configuration port: v.optional(v.number()), apiKey: v.optional(v.string()),
// Status tracking isActive: v.boolean(), createdAt: v.number(), updatedAt: v.number(),}) .index("by_name", ["name"]) .index("by_ip", ["ip"]) .index("by_fqdn", ["fqdn"]) .index("by_active", ["isActive"]) .index("by_vlan", ["vlan"])Server Fields
Section titled “Server Fields”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the server |
fqdn | string | Yes | Fully qualified domain name |
ip | string | Yes | IP address |
applications | string[] | Yes | Supported applications |
os | string | Yes | Operating system |
licenseServerType | string | Yes | Type (FlexLM, RLM, sesinetd) |
vlan | string | Yes | Network VLAN |
physicalVirtual | string | No | Physical or Virtual |
port | number | No | Connection port |
apiKey | string | No | API key for authentication |
isActive | boolean | Yes | Whether server is active |
createdAt | number | Yes | Creation timestamp |
updatedAt | number | Yes | Last update timestamp |
Server Health Checks Table
Section titled “Server Health Checks Table”Stores timestamped health check results:
serverHealthChecks: defineTable({ serverId: v.id("servers"), timestamp: v.number(), status: v.union( v.literal("online"), v.literal("offline"), v.literal("warning"), v.literal("maintenance"), v.literal("unavailable"), v.literal("connecting") ), responseTime: v.optional(v.number()), uptime: v.optional(v.number()), version: v.optional(v.string()), lastRestart: v.optional(v.number()), errorMessage: v.optional(v.string()), checks: v.array( v.object({ name: v.string(), status: v.string(), details: v.optional(v.string()), }) ), metrics: v.optional( v.object({ cpuUsage: v.optional(v.number()), memoryUsage: v.optional(v.number()), diskUsage: v.optional(v.number()), }) ),}) .index("by_server", ["serverId"]) .index("by_timestamp", ["timestamp"]) .index("by_server_timestamp", ["serverId", "timestamp"]) .index("by_status", ["status"])Health Check Status Values
Section titled “Health Check Status Values”| Status | Description |
|---|---|
online | Server responding normally |
offline | Server not responding |
warning | Server responding with issues |
maintenance | Server in maintenance mode |
unavailable | Cannot connect to server |
connecting | Connection in progress |
License Snapshots Table
Section titled “License Snapshots Table”Stores periodic captures of license utilization:
licenseSnapshots: defineTable({ serverId: v.id("servers"), timestamp: v.number(), features: v.array( v.object({ name: v.string(), vendor: v.optional(v.string()), version: v.optional(v.string()), total: v.number(), used: v.number(), available: v.number(), reserved: v.optional(v.number()), expirationDate: v.optional(v.number()), }) ), summary: v.object({ totalFeatures: v.number(), totalLicenses: v.number(), usedLicenses: v.number(), availableLicenses: v.number(), utilizationRate: v.number(), activeUsers: v.number(), }),}) .index("by_server", ["serverId"]) .index("by_timestamp", ["timestamp"]) .index("by_server_timestamp", ["serverId", "timestamp"])License Feature Schema
Section titled “License Feature Schema”interface LicenseFeature { name: string; // Feature name (e.g., "Maya_Complete") vendor?: string; // Vendor name (e.g., "Autodesk") version?: string; // Feature version total: number; // Total licenses available used: number; // Currently in use available: number; // Available for checkout reserved?: number; // Reserved licenses expirationDate?: number; // Expiration timestamp}License Sessions Table
Section titled “License Sessions Table”Tracks active license checkouts:
licenseSessions: defineTable({ serverId: v.id("servers"), sessionId: v.string(), userId: v.string(), userName: v.optional(v.string()), displayName: v.optional(v.string()), featureName: v.string(), hostname: v.optional(v.string()), ipAddress: v.optional(v.string()), processId: v.optional(v.number()), checkoutTime: v.number(), lastActivity: v.optional(v.number()), isActive: v.boolean(),}) .index("by_server", ["serverId"]) .index("by_active", ["isActive"]) .index("by_server_active", ["serverId", "isActive"]) .index("by_user", ["userId"]) .index("by_feature", ["featureName"]) .index("by_session_id", ["sessionId"])Alerts Table
Section titled “Alerts Table”Stores generated alerts from threshold violations:
alerts: defineTable({ serverId: v.id("servers"), type: v.union( v.literal("low_availability"), v.literal("high_utilization"), v.literal("server_down"), v.literal("license_expiring"), v.literal("maintenance_required"), v.literal("connection_failed"), v.literal("response_slow") ), severity: v.union( v.literal("info"), v.literal("warning"), v.literal("error"), v.literal("critical") ), featureName: v.optional(v.string()), message: v.string(), details: v.optional(v.string()), timestamp: v.number(), acknowledged: v.boolean(), acknowledgedBy: v.optional(v.string()), acknowledgedAt: v.optional(v.number()), resolved: v.boolean(), resolvedAt: v.optional(v.number()),}) .index("by_server", ["serverId"]) .index("by_timestamp", ["timestamp"]) .index("by_unresolved", ["resolved"]) .index("by_severity", ["severity"]) .index("by_type", ["type"]) .index("by_server_unresolved", ["serverId", "resolved"])Alert Types
Section titled “Alert Types”| Type | Description |
|---|---|
low_availability | Available licenses below threshold |
high_utilization | License usage above threshold |
server_down | Server not responding |
license_expiring | License approaching expiration |
maintenance_required | Maintenance flag set |
connection_failed | Cannot connect to server |
response_slow | Response time exceeds threshold |
Status Logic
Section titled “Status Logic”Server status is determined by the following logic:
function determineServerStatus(server: Server): ServerStatus { const { extraInfo, contacts, ip, fqdn } = server;
// Check for maintenance mode if (extraInfo?.toLowerCase().includes('transition') || contacts?.toLowerCase().includes('transition')) { return 'maintenance'; }
// Check for warning conditions if (extraInfo?.toLowerCase().includes('needs') || extraInfo?.toLowerCase().includes('migrate')) { return 'warning'; }
// Check for offline conditions if (!ip || !fqdn) { return 'offline'; }
// Default to online return 'online';}Querying Data
Section titled “Querying Data”Using Convex Queries
Section titled “Using Convex Queries”import { query } from './_generated/server';import { v } from 'convex/values';
// Get all active serversexport const listActive = query({ handler: async (ctx) => { return await ctx.db .query('servers') .withIndex('by_active', (q) => q.eq('isActive', true)) .collect(); },});
// Get server with recent health checksexport const getWithHealth = query({ args: { serverId: v.id('servers') }, handler: async (ctx, { serverId }) => { const server = await ctx.db.get(serverId); if (!server) return null;
const healthChecks = await ctx.db .query('serverHealthChecks') .withIndex('by_server_timestamp', (q) => q.eq('serverId', serverId) ) .order('desc') .take(10);
return { ...server, healthChecks }; },});
// Get license utilization for a serverexport const getLicenseUtilization = query({ args: { serverId: v.id('servers') }, handler: async (ctx, { serverId }) => { const snapshot = await ctx.db .query('licenseSnapshots') .withIndex('by_server_timestamp', (q) => q.eq('serverId', serverId) ) .order('desc') .first();
return snapshot; },});Real-Time Subscriptions
Section titled “Real-Time Subscriptions”import { useQuery } from 'convex/react';import { api } from '@/convex/_generated/api';
function ServerList() { // Automatically updates when data changes const servers = useQuery(api.servers.listActive);
if (servers === undefined) { return <Loading />; }
return ( <ul> {servers.map((server) => ( <ServerCard key={server._id} server={server} /> ))} </ul> );}Data Retention
Section titled “Data Retention”Retention Policies
Section titled “Retention Policies”| Data Type | Retention Period | Notes |
|---|---|---|
| Health Checks | 30 days | Aggregated to hourly after 7 days |
| License Snapshots | 90 days | Aggregated to daily after 30 days |
| License Sessions | 30 days | Active sessions never deleted |
| Alerts | 180 days | Resolved alerts can be purged |
| Server Config | Indefinite | Soft delete with isActive: false |
Cleanup Jobs
Section titled “Cleanup Jobs”import { cronJobs } from 'convex/server';import { internal } from './_generated/api';
const crons = cronJobs();
// Clean up old health checks daily at 2 AMcrons.daily( 'cleanup-health-checks', { hourUTC: 2, minuteUTC: 0 }, internal.maintenance.cleanupHealthChecks, { retentionDays: 30 });
// Clean up old license snapshots weeklycrons.weekly( 'cleanup-license-snapshots', { dayOfWeek: 'sunday', hourUTC: 3, minuteUTC: 0 }, internal.maintenance.cleanupLicenseSnapshots, { retentionDays: 90 });
export default crons;Data Validation
Section titled “Data Validation”Convex Validators
Section titled “Convex Validators”// convex/schema.ts - Using Convex validatorsimport { v } from 'convex/values';
// Server input validationconst serverValidator = { name: v.string(), fqdn: v.string(), ip: v.string(), applications: v.array(v.string()), os: v.string(), licenseServerType: v.string(), vlan: v.string(),};
// Health check status validatorconst statusValidator = v.union( v.literal('online'), v.literal('offline'), v.literal('warning'), v.literal('maintenance'), v.literal('unavailable'), v.literal('connecting'));Next Steps
Section titled “Next Steps”- Architecture - System architecture overview
- Real-Time Streaming - WebSocket and SSE integration
- Configuration - Environment configuration