summary history files

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;
}