Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 44 additions & 25 deletions packages/grid_client/src/modules/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,10 @@ class Contracts {
*
* @returns {Promise<number>} - A promise that resolves to the overdue amount for the specified contract.
*/
@validateInput
private async getContractOverdueAmount(contract: GridProxyContract, proxy: GridProxyClient) {
if (!contract || !contract.contract_id || !contract.type) {
throw new Error(`Invalid contract object: missing required properties`);
}
return await this.client.contracts.calculateContractOverDue({ contractInfo: contract, gridProxyClient: proxy });
}

Expand Down Expand Up @@ -681,32 +683,49 @@ class Contracts {
});
const rentedNodesIds: number[] = [];
for (const contract of contracts.data) {
switch (contract.type) {
case ContractType.Name:
result.nameContracts[contract.contract_id] = (
await this.getContractOverdueAmount(contract, proxy)
).toNumber();
result.totalOverdueAmount += result.nameContracts[contract.contract_id];
break;

case ContractType.Rent:
result.rentContracts[contract.contract_id] = (
await this.getContractOverdueAmount(contract, proxy)
).toNumber();
rentedNodesIds.push(contract.details.nodeId);
result.totalOverdueAmount += result.rentContracts[contract.contract_id];
break;

default:
/** skip node contracts on rented nodes as it already calculated in the rent contract */
if (rentedNodesIds.includes(contract.details.nodeId)) {
result.nodeContracts[contract.contract_id] = 0;
} else {
result.nodeContracts[contract.contract_id] = (
try {
switch (contract.type) {
case ContractType.Name:
result.nameContracts[contract.contract_id] = (
await this.getContractOverdueAmount(contract, proxy)
).toNumber();
result.totalOverdueAmount += result.nodeContracts[contract.contract_id];
}
result.totalOverdueAmount += result.nameContracts[contract.contract_id];
break;

case ContractType.Rent:
result.rentContracts[contract.contract_id] = (
await this.getContractOverdueAmount(contract, proxy)
).toNumber();
rentedNodesIds.push(contract.details.nodeId);
result.totalOverdueAmount += result.rentContracts[contract.contract_id];
break;

default:
/** skip node contracts on rented nodes as it already calculated in the rent contract */
if (rentedNodesIds.includes(contract.details.nodeId)) {
result.nodeContracts[contract.contract_id] = 0;
} else {
result.nodeContracts[contract.contract_id] = (
await this.getContractOverdueAmount(contract, proxy)
).toNumber();
result.totalOverdueAmount += result.nodeContracts[contract.contract_id];
}
}
} catch (error) {
console.log(`Failed to get overdue amount for contract ${contract.contract_id}:`, error);
switch (contract.type) {
case ContractType.Name:
result.nameContracts[contract.contract_id] = 0;
break;
case ContractType.Rent:
result.rentContracts[contract.contract_id] = 0;
if (contract.details?.nodeId) {
rentedNodesIds.push(contract.details.nodeId);
}
break;
default:
result.nodeContracts[contract.contract_id] = 0;
}
}
}
return result;
Expand Down
56 changes: 47 additions & 9 deletions packages/playground/src/weblets/tf_contracts_list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<section class="d-flex align-center">
<v-spacer />
<v-btn
v-if="lockedContracts?.totalOverdueAmount && !isLoading"
v-if="(totalOverdueAmount > 0 || hasGracePeriodContracts) && !isLoading"
class="mr-2"
color="warning"
prepend-icon="mdi-lock-open"
Expand Down Expand Up @@ -91,7 +91,12 @@
</template>
</v-card>
<!-- locked amount Dialog -->
<v-dialog v-if="lockedContracts?.totalOverdueAmount" v-model="unlockDialog" width="800" attach="#modals">
<v-dialog
v-if="totalOverdueAmount > 0 || hasGracePeriodContracts"
v-model="unlockDialog"
width="800"
attach="#modals"
>
<v-card>
<v-card-title class="bg-primary">
Unlock All Contracts
Expand All @@ -118,30 +123,39 @@
<v-divider class="mt-3" />
</v-card-text>
<v-card-text v-else>
<v-alert class="my-4" type="warning" variant="tonal">
<div v-if="lockedContracts?.totalOverdueAmount < freeBalance">
<v-alert v-if="totalOverdueAmount > 0" class="my-4" type="warning" variant="tonal">
<div v-if="totalOverdueAmount < freeBalance">
You have enough balance to unlock your contracts, this will cost you around
<span class="font-weight-bold">{{ Math.ceil(lockedContracts?.totalOverdueAmount) }}</span> TFTs.
<span class="font-weight-bold">{{ Math.ceil(totalOverdueAmount) }}</span> TFTs.
</div>
<div v-else>
<div>
Please fund your account with
<span class="font-weight-bold">
{{ Math.ceil(lockedContracts?.totalOverdueAmount - freeBalance) }}
{{ Math.ceil(totalOverdueAmount - freeBalance) }}
TFTs
</span>
</div>
Note that this amount will allow you to resume the contracts for up to one hour only. Make sure to complete
the funding promptly to avoid any interruptions!
</div>
</v-alert>
<v-alert v-else-if="hasGracePeriodContracts" class="my-4" type="info" variant="tonal">
<div>
You have contracts in grace period, but we couldn't retrieve the exact unlock cost. You can still try to
unlock them using the general unlock functionality.
</div>
<div class="mt-2">
<strong>Note:</strong> Make sure you have sufficient balance in your account before proceeding.
</div>
</v-alert>
<v-divider class="mt-3" />
</v-card-text>
<v-card-actions class="justify-end mb-1 mr-2">
<v-btn color="anchor" @click="unlockDialog = false"> Close </v-btn>
<v-tooltip
:text="
freeBalance < lockedContracts?.totalOverdueAmount
totalOverdueAmount > 0 && freeBalance < totalOverdueAmount
? `You don't have enough balance to unlock your contracts`
: `Get your contracts ready again`
"
Expand All @@ -150,7 +164,7 @@
<template #activator="{ props }">
<div v-bind="props">
<v-btn
:disabled="freeBalance < lockedContracts.totalOverdueAmount || loadingLockDetails"
:disabled="(totalOverdueAmount > 0 && freeBalance < totalOverdueAmount) || loadingLockDetails"
color="warning"
:loading="unlockContractLoading"
class="ml-2"
Expand Down Expand Up @@ -235,6 +249,7 @@

<script lang="ts" setup>
import type { ContractsOverdue, GridClient } from "@threefold/grid_client";
import { ContractStates } from "@threefold/grid_client";
import { type Contract, ContractState, NodeStatus, SortByContracts, SortOrder } from "@threefold/gridproxy_client";
import { DeploymentKeyDeletionError } from "@threefold/types";
import { computed, defineComponent, onMounted, type Ref, ref } from "vue";
Expand Down Expand Up @@ -463,6 +478,14 @@ const nodeStatus = computed(() => {
return statusObject;
});

const hasGracePeriodContracts = computed(() => {
return contracts.value.some(contract => contract.state === ContractStates.GracePeriod);
});

const totalOverdueAmount = computed(() => {
return lockedContracts.value?.totalOverdueAmount || 0;
});

// Calculate the total cost of contracts
async function getTotalCost() {
try {
Expand Down Expand Up @@ -503,7 +526,22 @@ async function onDeletedContracts(_contracts: NormalizedContract[]) {
contracts.value = [...rentContracts.value, ...nameContracts.value, ...nodeContracts.value];
}
async function getContractsLockDetails() {
lockedContracts.value = await grid.contracts.getTotalOverdue();
try {
lockedContracts.value = await grid.contracts.getTotalOverdue();
} catch (error: any) {
console.log("Failed to get contracts lock details:", error);
lockedContracts.value = {
nameContracts: {},
nodeContracts: {},
rentContracts: {},
totalOverdueAmount: 0,
};
const hasGracePeriodContracts = contracts.value.some(contract => contract.state === ContractStates.GracePeriod);
if (hasGracePeriodContracts) {
loadingErrorMessage.value = "Unable to load contract lock details. Some unlock functionality may be limited.";
createCustomToast(loadingErrorMessage.value, ToastType.warning, {});
}
}
}

// Define base table headers for contracts tables
Expand Down