Repository: hashicorp/vault
File: ui/app/routes/vault/cluster/recovery/snapshots.ts (TypeScript)

Code before:

import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { hash } from 'rsvp';

import { SystemListStorageRaftSnapshotLoadListEnum } from '@hashicorp/vault-client-typescript';

import type ApiService from 'vault/services/api';
import type Capabilities from 'vault/services/capabilities';

export default class RecoverySnapshotsRoute extends Route {
  @service declare readonly api: ApiService;
  @service declare readonly capabilities: Capabilities;

  async model() {
    const path = 'sys/storage/raft/snapshot/snapshot-load';
    const capabilities = await this.capabilities.fetch([path]);

    const canLoadSnapshot = capabilities[path]?.canUpdate;

    try {
      const { keys } = await this.api.sys.systemListStorageRaftSnapshotLoad(
        SystemListStorageRaftSnapshotLoadListEnum.TRUE
      );

      const snapshots = await Promise.all(
        (keys ?? []).map((key: string) => this.api.sys.systemReadStorageRaftSnapshotLoadId(key))
      );

      return hash({
        snapshots,
        canLoadSnapshot,
      });
    } catch (e) {
      // return empty list of snapshots
      return hash({
        snapshots: [],
        canLoadSnapshot,
      });
    }
  }
}


Review comment (suggestion):
Instead of doing this here, we'll want to transition to the snapshot.snapshot route for the returned id and make this request there.

This way it leaves the index route extensible if we ever want to render a list view

Code after:

import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { SystemListStorageRaftSnapshotLoadListEnum } from '@hashicorp/vault-client-typescript';

import type ApiService from 'vault/services/api';
import type Capabilities from 'vault/services/capabilities';
import type RouterService from '@ember/routing/router-service';
import type { ModelFrom } from 'vault/vault/route';

type SnapshotRouteModel = ModelFrom<RecoverySnapshotsRoute>;

export default class RecoverySnapshotsRoute extends Route {
  @service declare readonly api: ApiService;
  @service declare readonly capabilities: Capabilities;
  @service declare readonly router: RouterService;

  async model() {
    const { canUpdate } = await this.capabilities.fetchPathCapabilities(
      'sys/storage/raft/snapshot/snapshot-load'
    );

    const snapshots = await this.fetchSnapshots();

    return {
      snapshots,
      canLoadSnapshot: canUpdate,
    };
  }

  afterModel(model: SnapshotRouteModel) {
    if (model.snapshots.length === 1) {
      const snapshot_id = model.snapshots[0];
      this.router.transitionTo('vault.cluster.recovery.snapshots.snapshot.manage', snapshot_id);
    }
  }

  // todo: If this req fails bc user cannot list snapshots, they cannot use the UI (confirm with product)
  async fetchSnapshots() {
    try {
      const { keys } = await this.api.sys.systemListStorageRaftSnapshotLoad(
        SystemListStorageRaftSnapshotLoadListEnum.TRUE
      );
      return keys as string[];
    } catch (e) {
      const error = await this.api.parseError(e);
      if (error.status === 404) {
        return [];
      }
      throw {
        httpStatus: error.status,