Skip to content

Commit

Permalink
feat: transfer cycles
Browse files Browse the repository at this point in the history
Signed-off-by: David Dal Busco <[email protected]>
  • Loading branch information
peterpeterparker committed Nov 19, 2023
1 parent ad99034 commit 4918c9c
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 32 deletions.
16 changes: 16 additions & 0 deletions src/frontend/src/lib/api/satellites.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,19 @@ export const deleteAssets = async ({
const { del_assets } = await getSatelliteActor(satelliteId);
return del_assets(collection);
};

export const depositCycles = async ({
satelliteId,
cycles_to_retain,
destinationId: destination_id
}: {
satelliteId: Principal;
cycles_to_retain: bigint;
destinationId: Principal;
}) => {
const { deposit_cycles } = await getSatelliteActor(satelliteId);
return deposit_cycles({
cycles_to_retain,
destination_id
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
const dispatch = createEventDispatcher();
const onDelete = () => dispatch('junoDelete');
const onTransferCycles = () => dispatch('junoTransferCycles');
let enabled = false;
$: enabled =
Expand All @@ -21,5 +22,7 @@
{#if enabled && DEV_FEATURES}
<div in:fade>
<button class="menu" on:click={onDelete}><IconDelete /> {$i18n.core.delete}</button>

<button class="menu" on:click={onTransferCycles}><IconDelete /> {$i18n.core.delete}</button>
</div>
{/if}
41 changes: 41 additions & 0 deletions src/frontend/src/lib/components/canister/CanistersPicker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script lang="ts">
import { satellitesStore } from '$lib/stores/satellite.store';
import { satelliteName } from '$lib/utils/satellite.utils';
import { i18n } from '$lib/stores/i18n.store';
import type { Principal } from '@dfinity/principal';
import { isNullish, nonNullish } from '@dfinity/utils';
import { missionControlStore } from '$lib/stores/mission-control.store';
import type { Satellite } from '$declarations/mission_control/mission_control.did';
import { orbiterStore } from '$lib/stores/orbiter.store';
export let excludeSegmentId: Principal;
export let segmentIdText: string | undefined = undefined;
let excludeSegmentIdText: string;
$: excludeSegmentIdText = excludeSegmentId.toText();
let satellites: Satellite[];
$: satellites = ($satellitesStore ?? []).filter(
({ satellite_id }) => satellite_id.toText() !== excludeSegmentIdText
);
</script>

<select id="segment" name="segment" bind:value={segmentIdText}>
{#if isNullish(segmentIdText)}
<option value={undefined}>{$i18n.analytics.all_satellites}</option>
{/if}

{#if nonNullish($missionControlStore) && $missionControlStore.toText() !== excludeSegmentIdText}
<option value={$missionControlStore.toText()}>{$i18n.mission_control.title}</option>
{/if}

{#if nonNullish($orbiterStore) && $orbiterStore.orbiter_id.toText() !== excludeSegmentIdText}
<option value={$orbiterStore.orbiter_id.toText()}>{$i18n.analytics.title}</option>
{/if}

{#each satellites as satellite}
{@const satName = satelliteName(satellite)}

<option value={satellite.satellite_id.toText()}>{satName}</option>
{/each}
</select>
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
<script lang="ts">
import { i18n } from '$lib/stores/i18n.store';
import SpinnerModal from '$lib/components/ui/SpinnerModal.svelte';
import Modal from '$lib/components/ui/Modal.svelte';
import { createEventDispatcher } from 'svelte';
import { isBusy, wizardBusy } from '$lib/stores/busy.store';
import Value from '$lib/components/ui/Value.svelte';
import IconWarning from '$lib/components/icons/IconWarning.svelte';
import Input from '$lib/components/ui/Input.svelte';
import { authSignedInStore } from '$lib/stores/auth.store';
import { toasts } from '$lib/stores/toasts.store';
import { isNullish } from '@dfinity/utils';
import { missionControlStore } from '$lib/stores/mission-control.store';
import { Principal } from '@dfinity/principal';
import { ONE_TRILLION } from '$lib/constants/constants';
import { i18nFormat } from '$lib/utils/i18n.utils';
import { formatTCycles } from '$lib/utils/cycles.utils';
import { emit } from '$lib/utils/events.utils';
import CanistersPicker from '$lib/components/canister/CanistersPicker.svelte';
export let canisterId: Principal;
export let segment: 'satellite' | 'analytics' | 'mission_control';
export let currentCycles: bigint;
export let transferFn: (params: {
missionControlId: Principal;
cycles_to_retain: bigint;
destinationId: Principal;
}) => Promise<void>;
let steps: 'init' | 'in_progress' | 'ready' | 'error' = 'init';
const dispatch = createEventDispatcher();
const close = () => dispatch('junoClose');
let tCycles: string;
let cycles: bigint;
$: (() => {
cycles = BigInt(parseFloat(tCycles ?? 0) * ONE_TRILLION);
})();
let depositCycles: bigint;
$: depositCycles = currentCycles - cycles > 0 ? currentCycles - cycles : 0n;
let destinationId: string | undefined;
let validConfirm = false;
$: validConfirm = cycles > 0 && cycles <= currentCycles;
const onSubmit = async () => {
if (!$authSignedInStore) {
toasts.error({
text: $i18n.errors.no_identity
});
return;
}
if (isNullish($missionControlStore)) {
toasts.error({
text: $i18n.errors.no_mission_control
});
return;
}
if (cycles > currentCycles) {
toasts.error({
text: $i18n.canisters.invalid_cycles_to_retain
});
return;
}
if (isNullish(destinationId)) {
// TODO
toasts.error({
text: $i18n.canisters.invalid_cycles_to_retain
});
return;
}
steps = 'in_progress';
wizardBusy.start();
try {
await transferFn({
missionControlId: $missionControlStore,
cycles_to_retain: cycles,
destinationId: Principal.fromText(destinationId)
});
emit({ message: 'junoRestartCycles', detail: { canisterId } });
steps = 'ready';
} catch (err: unknown) {
steps = 'error';
// TODO
toasts.error({
text: $i18n.errors.canister_delete,
detail: err
});
}
wizardBusy.stop();
};
</script>

<Modal on:junoClose>
{#if steps === 'ready'}
<div class="msg">
<slot name="outro" />
<button on:click={close}>{$i18n.core.close}</button>
</div>
{:else if steps === 'in_progress'}
<SpinnerModal>
<p>{$i18n.canisters.delete_in_progress}</p>
</SpinnerModal>
{:else}
<form on:submit|preventDefault={onSubmit}>
<h2>
{@html i18nFormat($i18n.canisters.delete_title, [
{
placeholder: '{0}',
value: segment.replace('_', ' ')
}
])}
</h2>

<p>
{@html i18nFormat($i18n.canisters.delete_explanation, [
{
placeholder: '{0}',
value: segment.replace('_', ' ')
},
{
placeholder: '{1}',
value: segment.replace('_', ' ')
}
])}
</p>

<p>
{@html i18nFormat($i18n.canisters.delete_customization, [
{
placeholder: '{0}',
value: segment.replace('_', ' ')
},
{
placeholder: '{1}',
value: formatTCycles(currentCycles)
}
])}
</p>

<Value>
<svelte:fragment slot="label">{$i18n.canisters.cycles_to_retain}</svelte:fragment>

<CanistersPicker excludeSegmentId={canisterId} bind:segmentIdText={destinationId} />
</Value>

<Value ref="cycles">
<svelte:fragment slot="label">{$i18n.canisters.cycles_to_retain}</svelte:fragment>

<Input
name="cycles"
inputType="icp"
required
bind:value={tCycles}
placeholder={$i18n.canisters.amount}
/>
</Value>

<p>
<small
>{@html i18nFormat($i18n.canisters.cycles_to_transfer, [
{
placeholder: '{0}',
value: formatTCycles(depositCycles)
}
])}</small
>
</p>

<p class="warning">
<IconWarning />
{@html i18nFormat($i18n.canisters.delete_info, [
{
placeholder: '{0}',
value: segment.replace('_', ' ')
}
])}
</p>

<button type="submit" class="submit" disabled={$isBusy || !validConfirm}>
{$i18n.core.delete}
</button>
</form>
{/if}
</Modal>

<style lang="scss">
.warning {
padding: var(--padding) 0 0;
}
</style>
5 changes: 5 additions & 0 deletions src/frontend/src/lib/components/modals/Modals.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import OrbiterTopUpModal from '$lib/components/modals/OrbiterTopUpModal.svelte';
import SatelliteDeleteModal from '$lib/components/modals/SatelliteDeleteModal.svelte';
import OrbiterDeleteModal from '$lib/components/modals/OrbiterDeleteModal.svelte';
import SatelliteTransferCyclesModal from "$lib/components/modals/SatelliteTransferCyclesModal.svelte";
let modal: JunoModal | undefined = undefined;
Expand Down Expand Up @@ -68,3 +69,7 @@
{#if modal?.type === 'delete_orbiter' && nonNullish(modal.detail)}
<OrbiterDeleteModal on:junoClose={close} detail={modal.detail} />
{/if}

{#if modal?.type === 'transfer_cycles_satellite' && nonNullish(modal.detail)}
<SatelliteTransferCyclesModal on:junoClose={close} detail={modal.detail} />
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import type { JunoModalDeleteSatelliteDetail, JunoModalDetail } from '$lib/types/modal';
import type { Satellite } from '$declarations/mission_control/mission_control.did';
import type { Principal } from '@dfinity/principal';
import CanisterTransferCyclesModal from '$lib/components/modals/CanisterTransferCyclesModal.svelte';
import { depositCycles } from '$lib/api/satellites.api';
export let detail: JunoModalDetail;
let satellite: Satellite;
let currentCycles: bigint;
$: ({ satellite, cycles: currentCycles } = detail as JunoModalDeleteSatelliteDetail);
let transferFn: (params: {
missionControlId: Principal;
cycles_to_retain: bigint;
destinationId: Principal;
}) => Promise<void>;
$: transferFn = async (params: {
missionControlId: Principal;
cycles_to_retain: bigint;
destinationId: Principal;
}) =>
depositCycles({
...params,
satelliteId: satellite.satellite_id
});
</script>

<CanisterTransferCyclesModal
{transferFn}
{currentCycles}
canisterId={satellite.satellite_id}
on:junoClose
segment="satellite"
/>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Canister } from '$lib/types/canister';
import { emit } from '$lib/utils/events.utils';
import { createEventDispatcher } from 'svelte';
import CanisterDelete from '$lib/components/canister/CanisterDelete.svelte';
import CanisterDelete from '$lib/components/canister/CanisterActions.svelte';
export let canister: Canister | undefined = undefined;
Expand Down
24 changes: 22 additions & 2 deletions src/frontend/src/lib/components/satellites/SatelliteActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import type { Satellite } from '$declarations/mission_control/mission_control.did';
import TopUp from '$lib/components/canister/TopUp.svelte';
import CanisterStopStart from '$lib/components/canister/CanisterStopStart.svelte';
import SatelliteDelete from '$lib/components/satellites/SatelliteDelete.svelte';
import type { Canister } from '$lib/types/canister';
import Actions from '$lib/components/core/Actions.svelte';
import { emit } from '$lib/utils/events.utils';
import CanisterActions from '$lib/components/canister/CanisterActions.svelte';
export let satellite: Satellite;
Expand All @@ -22,6 +23,21 @@
let visible: boolean | undefined;
const close = () => (visible = false);
const onCanisterAction = (type: 'delete_satellite' | 'transfer_cycles_satellite') => {
close();
emit({
message: 'junoModal',
detail: {
type,
detail: {
satellite,
cycles: canister?.data?.cycles ?? 0n
}
}
});
};
</script>

<svelte:window on:junoSyncCanister={({ detail: { canister } }) => onSyncCanister(canister)} />
Expand All @@ -31,5 +47,9 @@

<CanisterStopStart {canister} segment="satellite" on:junoStop={close} on:junoStart={close} />

<SatelliteDelete {satellite} {canister} on:junoDelete={close} />
<CanisterActions
{canister}
on:junoDelete={() => onCanisterAction('delete_satellite')}
on:junoTransferCycles={() => onCanisterAction('transfer_cycles_satellite')}
/>
</Actions>
Loading

0 comments on commit 4918c9c

Please sign in to comment.