Skip to content

Data Model

License Server Detail uses Convex as its real-time database backend. This document describes the complete data model and schema.

The data model consists of five primary tables:

TablePurpose
serversLicense server configurations and metadata
serverHealthChecksHistorical health check results
licenseSnapshotsPeriodic license utilization snapshots
licenseSessionsActive license checkout sessions
alertsGenerated alerts and notifications

Stores license server configurations and connection information:

convex/schema.ts
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"])
FieldTypeRequiredDescription
namestringYesDisplay name for the server
fqdnstringYesFully qualified domain name
ipstringYesIP address
applicationsstring[]YesSupported applications
osstringYesOperating system
licenseServerTypestringYesType (FlexLM, RLM, sesinetd)
vlanstringYesNetwork VLAN
physicalVirtualstringNoPhysical or Virtual
portnumberNoConnection port
apiKeystringNoAPI key for authentication
isActivebooleanYesWhether server is active
createdAtnumberYesCreation timestamp
updatedAtnumberYesLast update timestamp

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"])
StatusDescription
onlineServer responding normally
offlineServer not responding
warningServer responding with issues
maintenanceServer in maintenance mode
unavailableCannot connect to server
connectingConnection in progress

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"])
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
}

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"])

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"])
TypeDescription
low_availabilityAvailable licenses below threshold
high_utilizationLicense usage above threshold
server_downServer not responding
license_expiringLicense approaching expiration
maintenance_requiredMaintenance flag set
connection_failedCannot connect to server
response_slowResponse time exceeds threshold

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';
}
convex/servers.ts
import { query } from './_generated/server';
import { v } from 'convex/values';
// Get all active servers
export 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 checks
export 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 server
export 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;
},
});
components/ServerList.tsx
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 TypeRetention PeriodNotes
Health Checks30 daysAggregated to hourly after 7 days
License Snapshots90 daysAggregated to daily after 30 days
License Sessions30 daysActive sessions never deleted
Alerts180 daysResolved alerts can be purged
Server ConfigIndefiniteSoft delete with isActive: false
convex/crons.ts
import { cronJobs } from 'convex/server';
import { internal } from './_generated/api';
const crons = cronJobs();
// Clean up old health checks daily at 2 AM
crons.daily(
'cleanup-health-checks',
{ hourUTC: 2, minuteUTC: 0 },
internal.maintenance.cleanupHealthChecks,
{ retentionDays: 30 }
);
// Clean up old license snapshots weekly
crons.weekly(
'cleanup-license-snapshots',
{ dayOfWeek: 'sunday', hourUTC: 3, minuteUTC: 0 },
internal.maintenance.cleanupLicenseSnapshots,
{ retentionDays: 90 }
);
export default crons;
// convex/schema.ts - Using Convex validators
import { v } from 'convex/values';
// Server input validation
const 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 validator
const statusValidator = v.union(
v.literal('online'),
v.literal('offline'),
v.literal('warning'),
v.literal('maintenance'),
v.literal('unavailable'),
v.literal('connecting')
);