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

100% Statements 46/46
100% Branches 18/18
100% Functions 17/17
100% Lines 45/45

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                            3x 13x 10x   3x 1x   2x 1x   1x     3x   13x                         3x               31x 31x   31x 18x 5x     13x     13x 13x     13x 13x 13x   13x 10x 10x 10x         13x         13x     31x   2x       1x 1x         31x   2x       1x 1x         31x   2x       1x 1x         31x   2x       1x   1x           31x                  
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;
  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],
  );
 
  return {
    hasVats,
    subclusters,
    pingVat,
    restartVat,
    terminateVat,
    terminateSubcluster,
  };
};