All files / kernel-ui/src/hooks useVats.ts

100% Statements 50/50
100% Branches 18/18
100% Functions 20/20
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161                            3x 15x 12x   3x 1x   2x 1x   1x     3x   15x                         3x                 35x 35x   35x 20x 5x     15x     15x 15x     15x 15x 15x   15x 12x 12x 12x         15x         15x     35x   2x       1x 1x         35x   2x       1x 1x         35x   2x       1x 1x         35x   2x       1x   1x           35x   2x       1x   1x           35x                    
import { stringify } from '@metamask/kernel-utils';
import type {
  VatConfig,
  VatId,
  Subcluster,
  KernelStatus,
} from '@metamask/ocap-kernel';
import { useCallback, useMemo, useState } from 'react';
 
import { usePanelContext } from '../context/PanelContext.tsx';
import type { VatRecord } from '../types.ts';
 
export type Subclusters = (Subcluster & { vatRecords: VatRecord[] })[];
 
const getSourceFromConfig = (config: VatConfig): string => {
  if ('bundleSpec' in config) {
    return config.bundleSpec;
  }
  if ('sourceSpec' in config) {
    return config.sourceSpec;
  }
  if ('bundleName' in config) {
    return config.bundleName;
  }
  return 'unknown';
};
 
const transformVatData = (
  vatData: KernelStatus['vats'][number],
): VatRecord => ({
  id: vatData.id,
  source: getSourceFromConfig(vatData.config),
  parameters: stringify(vatData.config?.parameters ?? {}, 0),
  creationOptions: stringify(vatData.config?.creationOptions ?? {}, 0),
  subclusterId: vatData.subclusterId,
});
 
/**
 * Hook to manage the vats state, grouped by subcluster.
 *
 * @returns An object containing the grouped vats and functions to interact with them.
 */
export const useVats = (): {
  subclusters: Subclusters;
  pingVat: (id: VatId) => void;
  restartVat: (id: VatId) => void;
  terminateVat: (id: VatId) => void;
  terminateSubcluster: (id: string) => void;
  reloadSubcluster: (id: string) => void;
  hasVats: boolean;
} => {
  const { callKernelMethod, status, logMessage } = usePanelContext();
  const [hasVats, setHasVats] = useState(false);
 
  const subclusters = useMemo<Subclusters>(() => {
    if (!status) {
      return [];
    }
 
    setHasVats(status.vats.length > 0);
 
    // Create a map of vat records for quick lookup
    const vatRecords = new Map<VatId, VatRecord>();
    const subclusterVats = new Map<string, VatRecord[]>();
 
    // First pass: transform all vats and group them by subcluster
    for (const vat of status.vats) {
      const vatRecord = transformVatData(vat);
      vatRecords.set(vat.id, vatRecord);
 
      if (vat.subclusterId) {
        const vats = subclusterVats.get(vat.subclusterId) ?? [];
        vats.push(vatRecord);
        subclusterVats.set(vat.subclusterId, vats);
      }
    }
 
    // Second pass: create subclusters with their vat records
    const subclustersWithVats = status.subclusters.map((subcluster) => ({
      ...subcluster,
      vatRecords: subclusterVats.get(subcluster.id) ?? [],
    }));
 
    return subclustersWithVats;
  }, [status]);
 
  const pingVat = useCallback(
    (id: VatId) => {
      callKernelMethod({
        method: 'pingVat',
        params: { id },
      })
        .then((result) => logMessage(stringify(result), 'success'))
        .catch((error) => logMessage(error.message, 'error'));
    },
    [callKernelMethod, logMessage],
  );
 
  const restartVat = useCallback(
    (id: VatId) => {
      callKernelMethod({
        method: 'restartVat',
        params: { id },
      })
        .then(() => logMessage(`Restarted vat "${id}"`, 'success'))
        .catch(() => logMessage(`Failed to restart vat "${id}"`, 'error'));
    },
    [callKernelMethod, logMessage],
  );
 
  const terminateVat = useCallback(
    (id: VatId) => {
      callKernelMethod({
        method: 'terminateVat',
        params: { id },
      })
        .then(() => logMessage(`Terminated vat "${id}"`, 'success'))
        .catch(() => logMessage(`Failed to terminate vat "${id}"`, 'error'));
    },
    [callKernelMethod, logMessage],
  );
 
  const terminateSubcluster = useCallback(
    (id: string) => {
      callKernelMethod({
        method: 'terminateSubcluster',
        params: { id },
      })
        .then(() => logMessage(`Terminated subcluster "${id}"`, 'success'))
        .catch(() =>
          logMessage(`Failed to terminate subcluster "${id}"`, 'error'),
        );
    },
    [callKernelMethod, logMessage],
  );
 
  const reloadSubcluster = useCallback(
    (id: string) => {
      callKernelMethod({
        method: 'reloadSubcluster',
        params: { id },
      })
        .then(() => logMessage(`Reloaded subcluster "${id}"`, 'success'))
        .catch(() =>
          logMessage(`Failed to reload subcluster "${id}"`, 'error'),
        );
    },
    [callKernelMethod, logMessage],
  );
 
  return {
    hasVats,
    subclusters,
    pingVat,
    restartVat,
    terminateVat,
    terminateSubcluster,
    reloadSubcluster,
  };
};