frontend/src/lib/lib.ts
import jsYaml from 'js-yaml';
/**
* Formats a Kubernetes resource into a human-readable description
* @param resource The Kubernetes resource to format
* @returns A formatted description string
*/
export function formatResourceDescription(resource: any): string {
if (!resource) {
return 'No resource data available';
}
let result = '';
// Extract metadata
const metadata = resource.metadata || {};
const spec = resource.spec || {};
const status = resource.status || {};
// Basic metadata
result += `Name: ${metadata.name || '<unknown>'}\n`;
result += `Namespace: ${metadata.namespace || '<unknown>'}\n`;
result += `Kind: ${resource.kind || '<unknown>'}\n`;
result += `API Version: ${resource.apiVersion || '<unknown>'}\n`;
// Creation timestamp
if (metadata.creationTimestamp) {
const creationDate = new Date(metadata.creationTimestamp);
result += `Created: ${creationDate.toUTCString()}\n`;
}
// Labels
result += 'Labels: ';
if (metadata.labels && Object.keys(metadata.labels).length > 0) {
result += '\n';
for (const [key, value] of Object.entries(metadata.labels)) {
result += ` ${key}=${value}\n`;
}
} else {
result += '<none>\n';
}
// Annotations
result += 'Annotations: ';
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
result += '\n';
for (const [key, value] of Object.entries(metadata.annotations)) {
result += ` ${key}=${value}\n`;
}
} else {
result += '<none>\n';
}
// Check if it's a Pod
const isPod = resource.kind === 'Pod';
// Check if it's a Deployment
const isDeployment = resource.kind === 'Deployment';
// Pod-specific information
if (isPod) {
// Pod status
const phase = status.phase || '<unknown>';
const podIP = status.podIP || '<none>';
const nodeName = spec.nodeName || '<none>';
result += `Status: ${phase}\n`;
result += `IP: ${podIP}\n`;
result += `Node: ${nodeName}\n`;
// Containers
result += 'Containers:\n';
const containers = spec.containers || [];
if (containers.length > 0) {
for (const container of containers) {
result += ` ${container.name}:\n`;
result += ` Image: ${container.image || '<none>'}\n`;
// Container ports
result += ' Ports: ';
const ports = container.ports || [];
if (ports.length > 0) {
result += '\n';
for (const port of ports) {
const protocol = port.protocol || 'TCP';
const portStr = ` ${protocol}:${port.containerPort}`;
if (port.name) {
result += `${portStr} (${port.name})\n`;
} else {
result += `${portStr}\n`;
}
}
} else {
result += '<none>\n';
}
// Container environment variables
result += ' Environment:';
const env = container.env || [];
if (env.length > 0) {
result += '\n';
for (const envVar of env) {
let envStr = ` ${envVar.name}=`;
if (envVar.value !== undefined) {
envStr += envVar.value;
} else if (envVar.valueFrom) {
envStr += '<set from source>';
} else {
envStr += '<unset>';
}
result += `${envStr}\n`;
}
} else {
result += ' <none>\n';
}
// Container resources
result += ' Resources: ';
const resources = container.resources || {};
if ((resources.limits && Object.keys(resources.limits).length > 0) ||
(resources.requests && Object.keys(resources.requests).length > 0)) {
result += '\n';
// Limits
if (resources.limits && Object.keys(resources.limits).length > 0) {
result += ' Limits:\n';
for (const [key, value] of Object.entries(resources.limits)) {
result += ` ${key}: ${value}\n`;
}
}
// Requests
if (resources.requests && Object.keys(resources.requests).length > 0) {
result += ' Requests:\n';
for (const [key, value] of Object.entries(resources.requests)) {
result += ` ${key}: ${value}\n`;
}
}
} else {
result += '<none>\n';
}
// Container status
const containerStatuses = status.containerStatuses || [];
for (const containerStatus of containerStatuses) {
if (containerStatus.name === container.name) {
result += ` Ready: ${containerStatus.ready}\n`;
result += ` Restarts: ${containerStatus.restartCount}\n`;
// Container state
result += ' State: ';
const state = containerStatus.state || {};
if (state.running) {
const startedAt = state.running.startedAt;
if (startedAt) {
const startDate = new Date(startedAt);
result += `Running since ${startDate.toUTCString()}\n`;
} else {
result += 'Running\n';
}
} else if (state.waiting) {
const reason = state.waiting.reason || '';
const message = state.waiting.message || '';
result += `Waiting: ${reason} - ${message}\n`;
} else if (state.terminated) {
const reason = state.terminated.reason || '';
const exitCode = state.terminated.exitCode || 0;
result += `Terminated: ${reason} (exit code: ${exitCode})\n`;
} else {
result += 'Unknown\n';
}
break;
}
}
}
} else {
result += ' <none>\n';
}
// Pod conditions
result += 'Conditions:\n';
const conditions = status.conditions || [];
if (conditions.length > 0) {
for (const condition of conditions) {
let condStr = ` ${condition.type}: ${condition.status}`;
if (condition.reason) {
condStr += ` (${condition.reason})`;
}
if (condition.message) {
condStr += ` - ${condition.message}`;
}
result += `${condStr}\n`;
}
} else {
result += ' <none>\n';
}
}
// Deployment-specific information
if (isDeployment) {
// Replicas
const replicas = spec.replicas !== undefined ? spec.replicas : 0;
result += `Replicas: ${replicas} desired`;
if (status) {
const updatedReplicas = status.updatedReplicas !== undefined ? status.updatedReplicas : 0;
const totalReplicas = status.replicas !== undefined ? status.replicas : 0;
const availableReplicas = status.availableReplicas !== undefined ? status.availableReplicas : 0;
const unavailableReplicas = status.unavailableReplicas !== undefined ? status.unavailableReplicas : 0;
result += ` | ${updatedReplicas} updated`;
result += ` | ${totalReplicas} total`;
result += ` | ${availableReplicas} available`;
result += ` | ${unavailableReplicas} unavailable`;
}
result += '\n';
// Strategy
let strategyType = 'Unknown';
if (spec.strategy && spec.strategy.type) {
strategyType = spec.strategy.type;
}
result += `Strategy: ${strategyType}\n`;
// Selector
result += 'Selector: ';
if (spec.selector && spec.selector.matchLabels && Object.keys(spec.selector.matchLabels).length > 0) {
result += '\n';
for (const [key, value] of Object.entries(spec.selector.matchLabels)) {
result += ` ${key}=${value}\n`;
}
} else {
result += '<none>\n';
}
// Pod template
if (spec.template) {
result += 'Pod Template:\n';
// Template labels
result += ' Labels: ';
if (spec.template.metadata && spec.template.metadata.labels &&
Object.keys(spec.template.metadata.labels).length > 0) {
result += '\n';
for (const [key, value] of Object.entries(spec.template.metadata.labels)) {
result += ` ${key}=${value}\n`;
}
} else {
result += '<none>\n';
}
// Template containers
result += ' Containers:\n';
if (spec.template.spec && spec.template.spec.containers && spec.template.spec.containers.length > 0) {
for (const container of spec.template.spec.containers) {
result += ` ${container.name}:\n`;
result += ` Image: ${container.image || '<none>'}\n`;
// Container ports
result += ' Ports: ';
const ports = container.ports || [];
if (ports.length > 0) {
result += '\n';
for (const port of ports) {
const protocol = port.protocol || 'TCP';
const portStr = ` ${protocol}:${port.containerPort}`;
if (port.name) {
result += `${portStr} (${port.name})\n`;
} else {
result += `${portStr}\n`;
}
}
} else {
result += '<none>\n';
}
// Container environment variables
result += ' Environment:';
const env = container.env || [];
if (env.length > 0) {
result += '\n';
for (const envVar of env) {
let envStr = ` ${envVar.name}=`;
if (envVar.value !== undefined) {
envStr += envVar.value;
} else if (envVar.valueFrom) {
envStr += '<set from source>';
} else {
envStr += '<unset>';
}
result += `${envStr}\n`;
}
} else {
result += ' <none>\n';
}
// Container resources
result += ' Resources: ';
const resources = container.resources || {};
if ((resources.limits && Object.keys(resources.limits).length > 0) ||
(resources.requests && Object.keys(resources.requests).length > 0)) {
result += '\n';
// Limits
if (resources.limits && Object.keys(resources.limits).length > 0) {
result += ' Limits:\n';
for (const [key, value] of Object.entries(resources.limits)) {
result += ` ${key}: ${value}\n`;
}
}
// Requests
if (resources.requests && Object.keys(resources.requests).length > 0) {
result += ' Requests:\n';
for (const [key, value] of Object.entries(resources.requests)) {
result += ` ${key}: ${value}\n`;
}
}
} else {
result += '<none>\n';
}
}
} else {
result += ' <none>\n';
}
}
// Deployment conditions
result += 'Conditions:\n';
const conditions = status.conditions || [];
if (conditions.length > 0) {
for (const condition of conditions) {
let condStr = ` ${condition.type}: ${condition.status}`;
if (condition.reason) {
condStr += ` (${condition.reason})`;
}
if (condition.message) {
condStr += ` - ${condition.message}`;
}
result += `${condStr}\n`;
}
} else {
result += ' <none>\n';
}
}
return result;
}
/**
* Determines if a resource is a Pod
* @param resource The Kubernetes resource to check
* @returns True if the resource is a Pod
*/
export function isPod(resource: any): boolean {
if (!resource) return false;
// Check if it's explicitly a Pod
if (resource.kind === 'Pod') return true;
// Check if it has containers in spec (Pod-like)
return !!(resource.spec && Array.isArray(resource.spec.containers));
}
/**
* Determines if a resource is a Deployment
* @param resource The Kubernetes resource to check
* @returns True if the resource is a Deployment
*/
export function isDeployment(resource: any): boolean {
if (!resource) return false;
// Check if it's explicitly a Deployment
if (resource.kind === 'Deployment') return true;
// Check if it has replicas in spec (Deployment-like)
return !!(resource.spec && typeof resource.spec.replicas !== 'undefined');
}
/**
* Extracts container names from a Kubernetes resource
* @param resource The Kubernetes resource object
* @returns Array of container names
*/
export function extractContainers(resource: any): string[] {
if (!resource) {
return [];
}
try {
// For Pod resources
if (isPod(resource)) {
const containers: string[] = [];
// Get containers
if (resource.spec?.containers && Array.isArray(resource.spec.containers)) {
containers.push(...resource.spec.containers.map((c: any) => c.name));
}
// Get init containers if they exist
if (resource.spec?.initContainers && Array.isArray(resource.spec.initContainers)) {
containers.push(...resource.spec.initContainers.map((c: any) => c.name));
}
// Get ephemeral containers if they exist
if (resource.spec?.ephemeralContainers && Array.isArray(resource.spec.ephemeralContainers)) {
containers.push(...resource.spec.ephemeralContainers.map((c: any) => c.name));
}
return containers;
}
// For Deployment resources - extract containers from template
if (isDeployment(resource)) {
const containers: string[] = [];
// Get containers from pod template
if (resource.spec?.template?.spec?.containers &&
Array.isArray(resource.spec.template.spec.containers)) {
containers.push(...resource.spec.template.spec.containers.map((c: any) => c.name));
}
// Get init containers from pod template if they exist
if (resource.spec?.template?.spec?.initContainers &&
Array.isArray(resource.spec.template.spec.initContainers)) {
containers.push(...resource.spec.template.spec.initContainers.map((c: any) => c.name));
}
return containers;
}
// For other resource types that might have containers
if (resource.spec?.containers && Array.isArray(resource.spec.containers)) {
return resource.spec.containers.map((c: any) => c.name);
}
// For StatefulSet, DaemonSet, Job, CronJob - similar structure to Deployment
if (resource.spec?.template?.spec?.containers &&
Array.isArray(resource.spec.template.spec.containers)) {
return resource.spec.template.spec.containers.map((c: any) => c.name);
}
} catch (error) {
console.error('Error extracting containers:', error);
}
return [];
}
export function podJsonToYaml(podJson: any): string {
try {
// Create a clean copy of the pod object
const cleanPod = cleanPodForEditing(podJson);
// Convert to YAML
const yamlString = jsYaml.dump(cleanPod, {
indent: 2,
lineWidth: -1, // Don't wrap lines
noRefs: true, // Don't output YAML references
sortKeys: false // Preserve key order
});
return yamlString;
} catch (error) {
console.error('Error converting Pod JSON to YAML:', error);
return '# Error converting Pod to YAML format';
}
}
/**
* Cleans a Pod object for editing by removing runtime fields
* that shouldn't be edited by users
*
* @param pod - The Pod object to clean
* @returns A cleaned Pod object suitable for editing
*/
function cleanPodForEditing(pod: any): any {
// Create a deep copy to avoid modifying the original
const cleanPod = JSON.parse(JSON.stringify(pod));
// Ensure apiVersion and kind are set
cleanPod.apiVersion = 'v1';
cleanPod.kind = 'Pod';
// Remove status field completely
delete cleanPod.status;
// Remove runtime fields from metadata
if (cleanPod.metadata) {
delete cleanPod.metadata.resourceVersion;
delete cleanPod.metadata.uid;
delete cleanPod.metadata.generation;
delete cleanPod.metadata.creationTimestamp;
delete cleanPod.metadata.deletionTimestamp;
delete cleanPod.metadata.deletionGracePeriodSeconds;
delete cleanPod.metadata.managedFields;
delete cleanPod.metadata.selfLink;
delete cleanPod.metadata.ownerReferences;
// Remove cluster-specific annotations
if (cleanPod.metadata.annotations) {
Object.keys(cleanPod.metadata.annotations).forEach(key => {
if (key.startsWith('kubernetes.io/') ||
key.startsWith('kubectl.kubernetes.io/')) {
delete cleanPod.metadata.annotations[key];
}
});
// If annotations is now empty, remove it
if (Object.keys(cleanPod.metadata.annotations).length === 0) {
delete cleanPod.metadata.annotations;
}
}
}
return cleanPod;
}
import * as jsYaml from 'js-yaml';
/**
* Converts a Kubernetes Namespace JSON object to YAML format
*
* @param namespaceJson - The Namespace object in JSON format
* @returns A YAML string representation of the Namespace
*/
export function namespaceJsonToYaml(namespaceJson: any): string {
try {
// Create a clean copy of the namespace object
const cleanNamespace = cleanNamespaceForEditing(namespaceJson);
// Convert to YAML
const yamlString = jsYaml.dump(cleanNamespace, {
indent: 2,
lineWidth: -1, // Don't wrap lines
noRefs: true, // Don't output YAML references
sortKeys: false // Preserve key order
});
return yamlString;
} catch (error) {
console.error('Error converting Namespace JSON to YAML:', error);
return '# Error converting Namespace to YAML format';
}
}
/**
* Cleans a Namespace object for editing by removing runtime fields
* that shouldn't be edited by users
*
* @param namespace - The Namespace object to clean
* @returns A cleaned Namespace object suitable for editing
*/
function cleanNamespaceForEditing(namespace: any): any {
// Create a deep copy to avoid modifying the original
const cleanNamespace = JSON.parse(JSON.stringify(namespace));
// Ensure apiVersion and kind are set
cleanNamespace.apiVersion = 'v1';
cleanNamespace.kind = 'Namespace';
// Remove status field completely
delete cleanNamespace.status;
// Remove runtime fields from metadata
if (cleanNamespace.metadata) {
delete cleanNamespace.metadata.resourceVersion;
delete cleanNamespace.metadata.uid;
delete cleanNamespace.metadata.generation;
delete cleanNamespace.metadata.creationTimestamp;
delete cleanNamespace.metadata.deletionTimestamp;
delete cleanNamespace.metadata.deletionGracePeriodSeconds;
delete cleanNamespace.metadata.managedFields;
delete cleanNamespace.metadata.selfLink;
delete cleanNamespace.metadata.ownerReferences;
// Remove cluster-specific annotations
if (cleanNamespace.metadata.annotations) {
Object.keys(cleanNamespace.metadata.annotations).forEach(key => {
if (key.startsWith('kubernetes.io/') ||
key.startsWith('kubectl.kubernetes.io/')) {
delete cleanNamespace.metadata.annotations[key];
}
});
// If annotations is now empty, remove it
if (Object.keys(cleanNamespace.metadata.annotations).length === 0) {
delete cleanNamespace.metadata.annotations;
}
}
}
// Remove finalizers if present (can cause issues when editing)
if (cleanNamespace.spec && cleanNamespace.spec.finalizers) {
delete cleanNamespace.spec.finalizers;
}
return cleanNamespace;
}
/**
* Converts a Kubernetes Secret JSON object to YAML format
*
* @param secretJson - The Secret object in JSON format
* @returns A YAML string representation of the Secret
*/
export function secretJsonToYaml(secretJson: any): string {
try {
// Create a clean copy of the secret object
const cleanSecret = cleanSecretForEditing(secretJson);
// Convert to YAML
const yamlString = jsYaml.dump(cleanSecret, {
indent: 2,
lineWidth: -1, // Don't wrap lines
noRefs: true, // Don't output YAML references
sortKeys: false // Preserve key order
});
return yamlString;
} catch (error) {
console.error('Error converting Secret JSON to YAML:', error);
return '# Error converting Secret to YAML format';
}
}
/**
* Cleans a Secret object for editing by removing runtime fields
* that shouldn't be edited by users
*
* @param secret - The Secret object to clean
* @returns A cleaned Secret object suitable for editing
*/
function cleanSecretForEditing(secret: any): any {
// Create a deep copy to avoid modifying the original
const cleanSecret = JSON.parse(JSON.stringify(secret));
// Ensure apiVersion and kind are set
cleanSecret.apiVersion = 'v1';
cleanSecret.kind = 'Secret';
// Remove status field completely
delete cleanSecret.status;
// Remove runtime fields from metadata
if (cleanSecret.metadata) {
delete cleanSecret.metadata.resourceVersion;
delete cleanSecret.metadata.uid;
delete cleanSecret.metadata.generation;
delete cleanSecret.metadata.creationTimestamp;
delete cleanSecret.metadata.deletionTimestamp;
delete cleanSecret.metadata.deletionGracePeriodSeconds;
delete cleanSecret.metadata.managedFields;
delete cleanSecret.metadata.selfLink;
delete cleanSecret.metadata.ownerReferences;
// Remove cluster-specific annotations
if (cleanSecret.metadata.annotations) {
Object.keys(cleanSecret.metadata.annotations).forEach(key => {
if (key.startsWith('kubernetes.io/') ||
key.startsWith('kubectl.kubernetes.io/')) {
delete cleanSecret.metadata.annotations[key];
}
});
// If annotations is now empty, remove it
if (Object.keys(cleanSecret.metadata.annotations).length === 0) {
delete cleanSecret.metadata.annotations;
}
}
}
// Set default type if not present
if (!cleanSecret.type) {
cleanSecret.type = 'Opaque';
}
// Convert data to stringData for easier editing
// This allows users to edit the values as plain text instead of base64
if (cleanSecret.data && Object.keys(cleanSecret.data).length > 0) {
cleanSecret.stringData = {};
// Decode base64 data to stringData
Object.keys(cleanSecret.data).forEach(key => {
try {
const decodedValue = atob(cleanSecret.data[key]);
cleanSecret.stringData[key] = decodedValue;
} catch (error) {
// If decoding fails, keep the original base64 value in data
console.warn(`Failed to decode base64 value for key "${key}":`, error);
}
});
// Remove the original data field since we're using stringData
delete cleanSecret.data;
}
// If there's existing stringData, preserve it
if (cleanSecret.stringData && Object.keys(cleanSecret.stringData).length === 0) {
delete cleanSecret.stringData;
}
// Ensure we have either data or stringData
if (!cleanSecret.data && !cleanSecret.stringData) {
cleanSecret.stringData = {};
}
return cleanSecret;
}