web/storagenode: payout info logic implementation
Change-Id: Ief1768f3987fdbf687fda5a90aa3c1c2320b17a1
This commit is contained in:
parent
6cac755916
commit
5c6c797737
@ -88,21 +88,21 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="info-area__payout-header">-->
|
||||
<!-- <p class="info-area__title">Payout</p>-->
|
||||
<!-- <router-link :to="PAYOUT_PATH" class="info-area__payout-header__link">-->
|
||||
<!-- <p class="info-area__payout-header__link__text">Payout Information</p>-->
|
||||
<!-- <BlueArrowRight />-->
|
||||
<!-- </router-link>-->
|
||||
<!-- </div>-->
|
||||
<div class="info-area__payout-header">
|
||||
<p class="info-area__title">Payout</p>
|
||||
<router-link :to="PAYOUT_PATH" class="info-area__payout-header__link">
|
||||
<p class="info-area__payout-header__link__text">Payout Information</p>
|
||||
<BlueArrowRight />
|
||||
</router-link>
|
||||
</div>
|
||||
<PayoutArea
|
||||
label="STORJ Wallet Address"
|
||||
:wallet-address="wallet"
|
||||
/>
|
||||
<!-- <section class="info-area__total-info-area">-->
|
||||
<!-- <SingleInfo width="48%" label="Total Earnings, Feb" value="$1.99" />-->
|
||||
<!-- <SingleInfo width="48%" label="Total Held Amount" value="$19.93" />-->
|
||||
<!-- </section>-->
|
||||
<section class="info-area__total-info-area">
|
||||
<SingleInfo width="48%" label="Total Earnings" :value="totalEarnings | centsToDollars" />
|
||||
<SingleInfo width="48%" label="Total Held Amount" :value="totalHeld | centsToDollars" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -166,6 +166,14 @@ export default class SNOContentFilling extends Vue {
|
||||
chart: HTMLElement;
|
||||
};
|
||||
|
||||
public get totalEarnings(): number {
|
||||
return this.$store.state.payoutModule.totalEarnings;
|
||||
}
|
||||
|
||||
public get totalHeld(): number {
|
||||
return this.$store.state.payoutModule.totalHeldAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used container size recalculation for charts resizing.
|
||||
*/
|
||||
|
@ -53,6 +53,7 @@ import StorjIcon from '@/../static/images/storjIcon.svg';
|
||||
import { RouteConfig } from '@/app/router';
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { NotificationsCursor } from '@/app/types/notifications';
|
||||
|
||||
const {
|
||||
@ -132,6 +133,8 @@ export default class SNOHeader extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(GET_NODE_INFO);
|
||||
await this.$store.dispatch(SELECT_SATELLITE, selectedSatellite);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO, selectedSatellite);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
|
||||
} catch (error) {
|
||||
console.error(`${error.message} satellite data.`);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import DisqualificationIcon from '@/../static/images/disqualify.svg';
|
||||
|
||||
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { SatelliteInfo } from '@/storagenode/dashboard';
|
||||
|
||||
@Component({
|
||||
@ -43,6 +44,8 @@ export default class SatelliteSelectionDropdown extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(NODE_ACTIONS.SELECT_SATELLITE, id);
|
||||
await this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_SATELLITE_SELECTION);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO, id);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL, id);
|
||||
} catch (error) {
|
||||
console.error(`${error.message} satellite data.`);
|
||||
}
|
||||
@ -52,6 +55,8 @@ export default class SatelliteSelectionDropdown extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(NODE_ACTIONS.SELECT_SATELLITE, null);
|
||||
await this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_SATELLITE_SELECTION);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
|
||||
} catch (error) {
|
||||
console.error(`${error.message} satellite data.`);
|
||||
}
|
||||
|
@ -8,40 +8,298 @@
|
||||
<EstimationPeriodDropdown />
|
||||
</div>
|
||||
<div class="estimation-container__divider"></div>
|
||||
<EstimationTable />
|
||||
<div class="estimation-container__payout-area">
|
||||
<div>
|
||||
<div class="estimation-table-container__labels-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__labels-area__text">Name</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2">
|
||||
<p class="estimation-table-container__labels-area__text">Type</p>
|
||||
</div>
|
||||
<div class="column justify-start column-3">
|
||||
<p class="estimation-table-container__labels-area__text">Price</p>
|
||||
</div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__labels-area__text">Disk</p>
|
||||
</div>
|
||||
<div class="column justify-start column-5">
|
||||
<p class="estimation-table-container__labels-area__text">Bandwidth</p>
|
||||
</div>
|
||||
<div class="column justify-end column-6">
|
||||
<p class="estimation-table-container__labels-area__text">Payout</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in data" :key="item.payout" class="estimation-table-container__info-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.name }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.type }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-3">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.price }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.disk }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-5">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.bandwidth }}</p>
|
||||
</div>
|
||||
<div class="column justify-end column-6">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.payout | centsToDollars }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="estimation-table-container__held-area">
|
||||
<p class="estimation-table-container__held-area__text">{{ heldInfo.surgePercent }}% Held back</p>
|
||||
<p class="estimation-table-container__held-area__text">-{{ held | centsToDollars }}</p>
|
||||
</div>
|
||||
<div class="estimation-table-container__total-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__total-area__text">TOTAL</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2"></div>
|
||||
<div class="column justify-start column-3"></div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__total-area__text">{{ totalDiskSpace }}m</p>
|
||||
</div>
|
||||
<div class="column justify-start column-5">
|
||||
<p class="estimation-table-container__total-area__text">{{ totalBandwidth }}</p>
|
||||
</div>
|
||||
<div class="column justify-end column-6">
|
||||
<p class="estimation-table-container__total-area__text">{{ totalPayout | centsToDollars }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="estimation-container__payout-area" v-if="isCurrentPeriod">
|
||||
<div class="estimation-container__payout-area__left-area">
|
||||
<p class="title-text">Estimated Payout</p>
|
||||
<p class="additional-text">At the end of the month if the load keeps the same for the rest of the month.</p>
|
||||
</div>
|
||||
<div class="estimation-container__payout-area__right-area">
|
||||
<p class="title-text">$25.93</p>
|
||||
<p class="additional-text">4 Mar 2020</p>
|
||||
<p class="title-text">{{ estimatedPayout | centsToDollars }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
import EstimationPeriodDropdown from '@/app/components/payments/EstimationPeriodDropdown.vue';
|
||||
import EstimationTable from '@/app/components/payments/EstimationTable.vue';
|
||||
|
||||
import { HeldInfo } from '@/app/types/payout';
|
||||
import { formatBytes, TB } from '@/app/utils/converter';
|
||||
|
||||
/**
|
||||
* Describes table row data item.
|
||||
*/
|
||||
class EstimationTableRow {
|
||||
public constructor(
|
||||
public name: string = '',
|
||||
public type: string = '',
|
||||
public price: string = '0',
|
||||
public disk: string = '',
|
||||
public bandwidth: string = '',
|
||||
public payout: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
EstimationTable,
|
||||
EstimationPeriodDropdown,
|
||||
},
|
||||
})
|
||||
export default class EstimationArea extends Vue {}
|
||||
export default class EstimationArea extends Vue {
|
||||
public now: Date = new Date();
|
||||
public data: EstimationTableRow[] = [];
|
||||
private BANDWIDTH_DOWNLOAD_PRICE_PER_TB = 2000;
|
||||
private BANDWIDTH_REPAIR_PRICE_PER_TB = 1000;
|
||||
private DISK_SPACE_PRICE_PER_TB = 150;
|
||||
|
||||
/**
|
||||
* Indicates if current month selected.
|
||||
*/
|
||||
public get isCurrentPeriod(): boolean {
|
||||
const end = this.$store.state.payoutModule.periodRange.end;
|
||||
const isCurrentMonthSelected = end.year === this.now.getUTCFullYear() && end.month === this.now.getUTCMonth();
|
||||
|
||||
return !this.$store.state.payoutModule.periodRange.start && isCurrentMonthSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns estimated payout depends on bandwidth and disk space in current month.
|
||||
*/
|
||||
@Watch('totalPayout')
|
||||
public get estimatedPayout(): number {
|
||||
const nodeStart = this.$store.state.node.info.startedAt;
|
||||
const daysInCurrentMonth: number = new Date(this.now.getFullYear(), this.now.getMonth() + 1, 0).getDate();
|
||||
|
||||
if (
|
||||
nodeStart.getUTCFullYear() === this.now.getUTCFullYear()
|
||||
&& nodeStart.getUTCMonth() === this.now.getUTCMonth()
|
||||
) {
|
||||
const remainingDays = daysInCurrentMonth - this.now.getUTCDate();
|
||||
const daysOnline = daysInCurrentMonth - nodeStart.getUTCDate();
|
||||
|
||||
return (this.totalPayout / daysOnline) * (daysOnline + remainingDays);
|
||||
}
|
||||
|
||||
return (this.totalPayout / this.now.getUTCDate()) * daysInCurrentMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns held info from store.
|
||||
*/
|
||||
public get heldInfo(): HeldInfo {
|
||||
return this.$store.state.payoutModule.heldInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns calculated or stored held amount.
|
||||
*/
|
||||
@Watch('isCurrentPeriod')
|
||||
@Watch('heldInfo')
|
||||
public get held(): number {
|
||||
if (this.isCurrentPeriod) {
|
||||
return (this.currentBandwidthDownload * this.BANDWIDTH_DOWNLOAD_PRICE_PER_TB
|
||||
+ this.currentBandwidthAuditAndRepair * this.BANDWIDTH_REPAIR_PRICE_PER_TB
|
||||
+ this.currentDiskSpace * this.DISK_SPACE_PRICE_PER_TB) / TB * this.heldInfo.surgePercent / 100;
|
||||
}
|
||||
|
||||
return this.heldInfo.held;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns calculated or stored total payout by selected period.
|
||||
*/
|
||||
@Watch('isCurrentPeriod')
|
||||
@Watch('heldInfo')
|
||||
public get totalPayout(): number {
|
||||
if (this.isCurrentPeriod) {
|
||||
return (this.currentBandwidthDownload * this.BANDWIDTH_DOWNLOAD_PRICE_PER_TB
|
||||
+ this.currentBandwidthAuditAndRepair * this.BANDWIDTH_REPAIR_PRICE_PER_TB
|
||||
+ this.currentDiskSpace * this.DISK_SPACE_PRICE_PER_TB) / TB
|
||||
- this.held;
|
||||
}
|
||||
|
||||
return this.$store.getters.totalPeriodPayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns calculated or stored total used disk space by selected period.
|
||||
*/
|
||||
@Watch('isCurrentPeriod')
|
||||
@Watch('heldInfo')
|
||||
public get totalDiskSpace(): string {
|
||||
if (this.isCurrentPeriod) {
|
||||
return formatBytes(this.currentDiskSpace);
|
||||
}
|
||||
|
||||
return formatBytes(this.heldInfo.usageAtRest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns calculated or stored total used bandwidth by selected period.
|
||||
*/
|
||||
@Watch('isCurrentPeriod')
|
||||
@Watch('heldInfo')
|
||||
public get totalBandwidth(): string {
|
||||
if (this.isCurrentPeriod) {
|
||||
return formatBytes(this.currentBandwidthAuditAndRepair + this.currentBandwidthDownload);
|
||||
}
|
||||
|
||||
const bandwidthSum = this.heldInfo.usageGet + this.heldInfo.usageGetRepair + this.heldInfo.usageGetAudit;
|
||||
|
||||
return formatBytes(bandwidthSum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns summary of current month audit and repair bandwidth.
|
||||
*/
|
||||
private get currentBandwidthAuditAndRepair(): number {
|
||||
if (!this.$store.state.node.egressChartData) return 0;
|
||||
|
||||
return this.$store.state.node.egressChartData.map(data => data.egress.audit + data.egress.repair).reduce((previous, current) => previous + current, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns summary of current month download bandwidth.
|
||||
*/
|
||||
private get currentBandwidthDownload(): number {
|
||||
if (!this.$store.state.node.egressChartData) return 0;
|
||||
|
||||
return this.$store.state.node.egressChartData.map(data => data.egress.usage)
|
||||
.reduce((previous, current) => previous + current, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns summary of current month used disk space.
|
||||
*/
|
||||
private get currentDiskSpace(): number {
|
||||
if (!this.$store.state.node.storageChartData) return 0;
|
||||
|
||||
return this.$store.state.node.storageChartData.map(data => data.atRestTotal).reduce((previous, current) => previous + current, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Builds estimated payout table.
|
||||
*/
|
||||
public mounted(): void {
|
||||
this.buildTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds estimated payout table depends on selected period.
|
||||
*/
|
||||
@Watch('heldInfo')
|
||||
@Watch('isCurrentPeriod')
|
||||
private buildTable(): void {
|
||||
if (!this.isCurrentPeriod) {
|
||||
this.data = [
|
||||
new EstimationTableRow('Download', 'Egress', `$${this.BANDWIDTH_DOWNLOAD_PRICE_PER_TB / 100} / TB`, '--', formatBytes(this.heldInfo.usageGet), this.heldInfo.compGet),
|
||||
new EstimationTableRow('Repair & Audit', 'Egress', `$${this.BANDWIDTH_REPAIR_PRICE_PER_TB / 100} / TB`, '--', formatBytes(this.heldInfo.usageGetRepair + this.heldInfo.usageGetAudit), this.heldInfo.compGetRepair + this.heldInfo.compGetAudit),
|
||||
new EstimationTableRow('Disk Average Month', 'Storage', `$${this.DISK_SPACE_PRICE_PER_TB / 100} / TBm`, formatBytes(this.heldInfo.usageAtRest) + 'm', '--', this.heldInfo.compAtRest),
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = [
|
||||
new EstimationTableRow(
|
||||
'Download',
|
||||
'Egress',
|
||||
`$${this.BANDWIDTH_DOWNLOAD_PRICE_PER_TB / 100} / TB`,
|
||||
'--',
|
||||
formatBytes(this.currentBandwidthDownload),
|
||||
this.currentBandwidthDownload * this.BANDWIDTH_DOWNLOAD_PRICE_PER_TB / TB,
|
||||
),
|
||||
new EstimationTableRow(
|
||||
'Repair & Audit',
|
||||
'Egress',
|
||||
`$${this.BANDWIDTH_REPAIR_PRICE_PER_TB / 100} / TB`,
|
||||
'--',
|
||||
formatBytes(this.currentBandwidthAuditAndRepair),
|
||||
this.currentBandwidthAuditAndRepair * this.BANDWIDTH_REPAIR_PRICE_PER_TB / TB,
|
||||
),
|
||||
new EstimationTableRow(
|
||||
'Disk Average Month',
|
||||
'Storage',
|
||||
`$${this.DISK_SPACE_PRICE_PER_TB / 100} / TBm`,
|
||||
this.totalDiskSpace + 'm',
|
||||
'--',
|
||||
this.currentDiskSpace * this.DISK_SPACE_PRICE_PER_TB / TB,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.estimation-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 28px 40px 0 40px;
|
||||
padding: 28px 40px 28px 40px;
|
||||
background: #fff;
|
||||
border: 1px solid #eaeaea;
|
||||
box-sizing: border-box;
|
||||
@ -108,4 +366,111 @@ export default class EstimationArea extends Vue {}
|
||||
line-height: 17px;
|
||||
color: #b5bdcb;
|
||||
}
|
||||
|
||||
.estimation-table-container {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__labels-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 17px;
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 36px;
|
||||
background: #f9fafc;
|
||||
|
||||
&__text {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #909bad;
|
||||
}
|
||||
}
|
||||
|
||||
&__info-area {
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid #a9b5c1;
|
||||
|
||||
&__text {
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
|
||||
&__held-area {
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 56px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__text {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
|
||||
&__total-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 56px;
|
||||
background: rgba(0, 117, 255, 0.05);
|
||||
|
||||
&__text {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.column-1 {
|
||||
width: 26.9%;
|
||||
}
|
||||
|
||||
.column-2 {
|
||||
width: 14.3%;
|
||||
}
|
||||
|
||||
.column-3 {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.column-4 {
|
||||
width: 18.2%;
|
||||
}
|
||||
|
||||
.column-5 {
|
||||
width: 18.9%;
|
||||
}
|
||||
|
||||
.column-6 {
|
||||
width: 8.7%;
|
||||
}
|
||||
</style>
|
||||
|
@ -4,12 +4,12 @@
|
||||
<template>
|
||||
<div class="period-container" @click.stop="openPeriodDropdown">
|
||||
<p class="period-container__label">{{ currentPeriod }}</p>
|
||||
<BlackArrowHide v-if="isDropDownShown" />
|
||||
<BlackArrowHide v-if="isCalendarShown" />
|
||||
<BlackArrowExpand v-else />
|
||||
<PayoutPeriodCalendar
|
||||
class="period-container__calendar"
|
||||
v-if="isDropDownShown"
|
||||
v-click-outside="closePeriodDropdown"
|
||||
v-if="isCalendarShown"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -22,6 +22,18 @@ import PayoutPeriodCalendar from '@/app/components/payments/PayoutPeriodCalendar
|
||||
import BlackArrowExpand from '@/../static/images/BlackArrowExpand.svg';
|
||||
import BlackArrowHide from '@/../static/images/BlackArrowHide.svg';
|
||||
|
||||
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { PayoutPeriod } from '@/app/types/payout';
|
||||
|
||||
/**
|
||||
* Holds all months names.
|
||||
*/
|
||||
const monthNames = [
|
||||
'January', 'February', 'March', 'April',
|
||||
'May', 'June', 'July', 'August',
|
||||
'September', 'October', 'November', 'December',
|
||||
];
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PayoutPeriodCalendar,
|
||||
@ -30,26 +42,37 @@ import BlackArrowHide from '@/../static/images/BlackArrowHide.svg';
|
||||
}
|
||||
})
|
||||
export default class EstimationPeriodDropdown extends Vue {
|
||||
public currentPeriod = new Date().toDateString();
|
||||
/**
|
||||
* Indicates if payout period selection dropdown should be rendered.
|
||||
* Returns formatted selected payout period.
|
||||
*/
|
||||
public isDropDownShown: boolean = false;
|
||||
public get currentPeriod(): string {
|
||||
const start: PayoutPeriod = this.$store.state.payoutModule.periodRange.start;
|
||||
const end: PayoutPeriod = this.$store.state.payoutModule.periodRange.end;
|
||||
|
||||
return start ?
|
||||
`${monthNames[start.month]}, ${start.year} - ${monthNames[end.month]}, ${end.year}`
|
||||
: `${monthNames[end.month]}, ${end.year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if period selection calendar should appear.
|
||||
*/
|
||||
public get isCalendarShown(): boolean {
|
||||
return this.$store.state.appStateModule.isPayoutCalendarShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens payout period selection dropdown.
|
||||
*/
|
||||
public openPeriodDropdown(): void {
|
||||
setTimeout(() => {
|
||||
this.isDropDownShown = true;
|
||||
}, 0);
|
||||
this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_CALENDAR, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes payout period selection dropdown.
|
||||
*/
|
||||
public closePeriodDropdown(): void {
|
||||
this.isDropDownShown = false;
|
||||
this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_CALENDAR, false);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,189 +0,0 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="estimation-table-container__labels-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__labels-area__text">Name</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2">
|
||||
<p class="estimation-table-container__labels-area__text">Type</p>
|
||||
</div>
|
||||
<div class="column justify-start column-3">
|
||||
<p class="estimation-table-container__labels-area__text">Disk</p>
|
||||
</div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__labels-area__text">Bandwidth</p>
|
||||
</div>
|
||||
<div class="column justify-end column-5">
|
||||
<p class="estimation-table-container__labels-area__text">Payout</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in data" class="estimation-table-container__info-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.name }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.type }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-3">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.disk }}</p>
|
||||
</div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.bandwidth }}</p>
|
||||
</div>
|
||||
<div class="column justify-end column-5">
|
||||
<p class="estimation-table-container__info-area__text">{{ item.payout }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="estimation-table-container__held-area">
|
||||
<p class="estimation-table-container__held-area__text">25% Held back</p>
|
||||
<p class="estimation-table-container__held-area__text">-$0.67</p>
|
||||
</div>
|
||||
<div class="estimation-table-container__total-area">
|
||||
<div class="column justify-start column-1">
|
||||
<p class="estimation-table-container__total-area__text">TOTAL</p>
|
||||
</div>
|
||||
<div class="column justify-start column-2"></div>
|
||||
<div class="column justify-start column-3">
|
||||
<p class="estimation-table-container__total-area__text">25.45 GBm</p>
|
||||
</div>
|
||||
<div class="column justify-start column-4">
|
||||
<p class="estimation-table-container__total-area__text">98.24 GB</p>
|
||||
</div>
|
||||
<div class="column justify-end column-5">
|
||||
<p class="estimation-table-container__total-area__text">$1.99</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
class EstimationTableRow {
|
||||
public constructor(
|
||||
public name: string = '',
|
||||
public type: string = '',
|
||||
public disk: string = '',
|
||||
public bandwidth: string = '',
|
||||
public payout: string = '',
|
||||
) {}
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class EstimationTable extends Vue {
|
||||
public data: EstimationTableRow[] = [
|
||||
new EstimationTableRow('Download', 'Egress', '--', '98.24 GB', '$1.94'),
|
||||
new EstimationTableRow('Download Repair', 'Egress', '--', '0.00 B', '$0.24'),
|
||||
new EstimationTableRow('Download Audit', 'Egress', '--', '22.53 KB', '$0.00'),
|
||||
new EstimationTableRow('Disk Average Month', 'Storage', '25.45 GBm', '--', '$0.48'),
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.estimation-table-container {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__labels-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 17px;
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 36px;
|
||||
background: #f9fafc;
|
||||
|
||||
&__text {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #909bad;
|
||||
}
|
||||
}
|
||||
|
||||
&__info-area {
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid #a9b5c1;
|
||||
|
||||
&__text {
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
|
||||
&__held-area {
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 56px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__text {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
|
||||
&__total-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 16px;
|
||||
width: calc(100% - 32px);
|
||||
height: 56px;
|
||||
background: rgba(0, 117, 255, 0.05);
|
||||
|
||||
&__text {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #535f77;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.column-1 {
|
||||
width: 26.9%;
|
||||
}
|
||||
|
||||
.column-2 {
|
||||
width: 18.3%;
|
||||
}
|
||||
|
||||
.column-3 {
|
||||
width: 22.2%;
|
||||
}
|
||||
|
||||
.column-4 {
|
||||
width: 23.9%;
|
||||
}
|
||||
|
||||
.column-5 {
|
||||
width: 8.7%;
|
||||
}
|
||||
</style>
|
@ -15,13 +15,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="held-progress__border"></div>
|
||||
<p class="held-progress__main-text">It is your <span class="bold">{{ '7' }} month</span> on network</p>
|
||||
<p class="held-progress__hint">25% of Storage Node revenue is withheld, 75% is paid to the Storage Node Operator</p>
|
||||
<p class="held-progress__main-text">It is your <span class="bold">{{ monthsOnNetwork }} month</span> on network</p>
|
||||
<!-- <p class="held-progress__hint">25% of Storage Node revenue is withheld, 75% is paid to the Storage Node Operator</p>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { Component, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
class HeldStep {
|
||||
public constructor(
|
||||
@ -41,13 +41,65 @@ class HeldStep {
|
||||
|
||||
@Component
|
||||
export default class HeldProgress extends Vue {
|
||||
public steps: HeldStep[] = [
|
||||
new HeldStep('75%', 'Month 1-3', false, false),
|
||||
new HeldStep('50%', 'Month 4-6', false, false),
|
||||
new HeldStep('25%', 'Month 7-9', true, false),
|
||||
new HeldStep('0%', 'Month 10-15', false, true),
|
||||
new HeldStep('+50%', 'Month 15', false, true),
|
||||
];
|
||||
public steps: HeldStep[] = [];
|
||||
|
||||
/**
|
||||
* Returns approximated number of months that node is online.
|
||||
*/
|
||||
public get monthsOnNetwork(): number {
|
||||
const now = new Date();
|
||||
const secondsInMonthApproximately = 2628000;
|
||||
const differenceInSeconds = (now.getTime() - this.$store.state.node.info.startedAt.getTime()) / 1000;
|
||||
|
||||
return Math.ceil(differenceInSeconds / secondsInMonthApproximately);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Builds held steps.
|
||||
*/
|
||||
public mounted(): void {
|
||||
this.buildSteps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds held steps depends on node`s months online.
|
||||
*/
|
||||
@Watch('monthsOnNetwork')
|
||||
private buildSteps(): void {
|
||||
this.steps = [
|
||||
new HeldStep(
|
||||
'75%',
|
||||
'Month 1-3',
|
||||
this.monthsOnNetwork > 0 && this.monthsOnNetwork <= 3,
|
||||
false,
|
||||
),
|
||||
new HeldStep(
|
||||
'50%',
|
||||
'Month 4-6',
|
||||
this.monthsOnNetwork > 3 && this.monthsOnNetwork <= 6,
|
||||
this.monthsOnNetwork < 4,
|
||||
),
|
||||
new HeldStep(
|
||||
'25%',
|
||||
'Month 7-9',
|
||||
this.monthsOnNetwork > 6 && this.monthsOnNetwork <= 9,
|
||||
this.monthsOnNetwork < 7,
|
||||
),
|
||||
new HeldStep(
|
||||
'0%',
|
||||
'Month 10-15',
|
||||
this.monthsOnNetwork > 9 && this.monthsOnNetwork <= 15,
|
||||
this.monthsOnNetwork < 10,
|
||||
),
|
||||
new HeldStep(
|
||||
'+50%',
|
||||
'Month 15',
|
||||
this.monthsOnNetwork > 15,
|
||||
this.monthsOnNetwork < 15,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="payout-period-calendar__footer-area">
|
||||
<p class="payout-period-calendar__footer-area__period">{{ period }}</p>
|
||||
<p class="payout-period-calendar__footer-area__ok-button">OK</p>
|
||||
<p class="payout-period-calendar__footer-area__ok-button" @click="submit">OK</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -38,6 +38,10 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import GrayArrowLeftIcon from '@/../static/images/payments/GrayArrowLeft.svg';
|
||||
|
||||
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { PayoutInfoRange, PayoutPeriod } from '@/app/types/payout';
|
||||
|
||||
interface StoredMonthsByYear {
|
||||
[key: number]: MonthButton[];
|
||||
}
|
||||
@ -99,6 +103,34 @@ export default class PayoutPeriodCalendar extends Vue {
|
||||
this.currentDisplayedMonths = this.displayedMonths[this.displayedYear];
|
||||
}
|
||||
|
||||
public async submit(): Promise<void> {
|
||||
if (!this.firstSelectedMonth) {
|
||||
this.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.secondSelectedMonth ? await this.$store.dispatch(
|
||||
PAYOUT_ACTIONS.SET_PERIODS_RANGE, new PayoutInfoRange(
|
||||
new PayoutPeriod(this.firstSelectedMonth.year, this.firstSelectedMonth.index),
|
||||
new PayoutPeriod(this.secondSelectedMonth.year, this.secondSelectedMonth.index),
|
||||
),
|
||||
) : await this.$store.dispatch(
|
||||
PAYOUT_ACTIONS.SET_PERIODS_RANGE, new PayoutInfoRange(
|
||||
null,
|
||||
new PayoutPeriod(this.firstSelectedMonth.year, this.firstSelectedMonth.index),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO, this.$store.state.node.selectedSatellite.id);
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates selected period label.
|
||||
*/
|
||||
@ -235,8 +267,8 @@ export default class PayoutPeriodCalendar extends Vue {
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const notBeforeNodeStart =
|
||||
(nodeStartedAt.getUTCFullYear() === year && nodeStartedAt.getUTCMonth() <= i)
|
||||
|| nodeStartedAt.getUTCFullYear() < year;
|
||||
nodeStartedAt.getUTCFullYear() < year
|
||||
|| (nodeStartedAt.getUTCFullYear() === year && nodeStartedAt.getUTCMonth() <= i);
|
||||
const inFuture = isCurrentYear && i > nowMonth;
|
||||
|
||||
const isMonthActive = notBeforeNodeStart && !inFuture;
|
||||
@ -245,6 +277,13 @@ export default class PayoutPeriodCalendar extends Vue {
|
||||
|
||||
this.displayedMonths[year] = months;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes calendar.
|
||||
*/
|
||||
private close(): void {
|
||||
setTimeout(() => this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_CALENDAR, false), 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -35,11 +35,11 @@ export const router = new Router({
|
||||
name: RouteConfig.Notifications.name,
|
||||
component: NotificationsArea
|
||||
},
|
||||
// {
|
||||
// path: RouteConfig.Payout.path,
|
||||
// name: RouteConfig.Payout.name,
|
||||
// component: PayoutArea
|
||||
// },
|
||||
{
|
||||
path: RouteConfig.Payout.path,
|
||||
name: RouteConfig.Payout.name,
|
||||
component: PayoutArea
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
name: '404',
|
||||
|
@ -5,12 +5,15 @@ import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import { makeNotificationsModule } from '@/app/store/modules/notifications';
|
||||
import { makePayoutModule } from '@/app/store/modules/payout';
|
||||
import { NotificationsHttpApi } from '@/storagenode/api/notifications';
|
||||
import { PayoutHttpApi } from '@/storagenode/api/payout';
|
||||
|
||||
import { appStateModule } from './modules/appState';
|
||||
import { node } from './modules/node';
|
||||
|
||||
const notificationsApi = new NotificationsHttpApi();
|
||||
const payoutApi = new PayoutHttpApi();
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -22,6 +25,7 @@ export const store = new Vuex.Store({
|
||||
node,
|
||||
appStateModule,
|
||||
notificationsModule: makeNotificationsModule(notificationsApi),
|
||||
payoutModule: makePayoutModule(payoutApi),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ export const APPSTATE_MUTATIONS = {
|
||||
TOGGLE_BANDWIDTH_CHART: 'TOGGLE_BANDWIDTH_CHART',
|
||||
TOGGLE_EGRESS_CHART: 'TOGGLE_EGRESS_CHART',
|
||||
TOGGLE_INGRESS_CHART: 'TOGGLE_INGRESS_CHART,',
|
||||
TOGGLE_PAYOUT_CALENDAR: 'TOGGLE_PAYOUT_CALENDAR',
|
||||
CLOSE_ADDITIONAL_CHARTS: 'CLOSE_ADDITIONAL_CHARTS',
|
||||
CLOSE_ALL_POPUPS: 'CLOSE_ALL_POPUPS',
|
||||
};
|
||||
@ -16,6 +17,7 @@ export const APPSTATE_ACTIONS = {
|
||||
TOGGLE_EGRESS_CHART: 'TOGGLE_EGRESS_CHART',
|
||||
TOGGLE_INGRESS_CHART: 'TOGGLE_INGRESS_CHART',
|
||||
CLOSE_ADDITIONAL_CHARTS: 'CLOSE_ADDITIONAL_CHARTS',
|
||||
TOGGLE_PAYOUT_CALENDAR: 'TOGGLE_PAYOUT_CALENDAR',
|
||||
CLOSE_ALL_POPUPS: 'CLOSE_ALL_POPUPS',
|
||||
};
|
||||
|
||||
@ -26,6 +28,7 @@ const {
|
||||
TOGGLE_INGRESS_CHART,
|
||||
CLOSE_ADDITIONAL_CHARTS,
|
||||
CLOSE_ALL_POPUPS,
|
||||
TOGGLE_PAYOUT_CALENDAR,
|
||||
} = APPSTATE_MUTATIONS;
|
||||
|
||||
export const appStateModule = {
|
||||
@ -34,6 +37,7 @@ export const appStateModule = {
|
||||
isBandwidthChartShown: true,
|
||||
isEgressChartShown: false,
|
||||
isIngressChartShown: false,
|
||||
isPayoutCalendarShown: false,
|
||||
},
|
||||
mutations: {
|
||||
[TOGGLE_SATELLITE_SELECTION](state: any): void {
|
||||
@ -48,6 +52,9 @@ export const appStateModule = {
|
||||
[TOGGLE_INGRESS_CHART](state: any): void {
|
||||
state.isIngressChartShown = !state.isIngressChartShown;
|
||||
},
|
||||
[TOGGLE_PAYOUT_CALENDAR](state: any, value): void {
|
||||
state.isPayoutCalendarShown = value;
|
||||
},
|
||||
[CLOSE_ADDITIONAL_CHARTS](state: any): void {
|
||||
state.isBandwidthChartShown = true;
|
||||
state.isIngressChartShown = false;
|
||||
@ -67,6 +74,9 @@ export const appStateModule = {
|
||||
|
||||
commit(APPSTATE_MUTATIONS.CLOSE_ALL_POPUPS);
|
||||
},
|
||||
[APPSTATE_ACTIONS.TOGGLE_PAYOUT_CALENDAR]: function ({commit, state}: any, value: boolean): void {
|
||||
commit(APPSTATE_MUTATIONS.TOGGLE_PAYOUT_CALENDAR, value);
|
||||
},
|
||||
[APPSTATE_ACTIONS.TOGGLE_EGRESS_CHART]: function ({commit, state}: any): void {
|
||||
if (!state.isBandwidthChartShown) {
|
||||
commit(APPSTATE_MUTATIONS.TOGGLE_EGRESS_CHART);
|
||||
|
@ -28,10 +28,6 @@ const {
|
||||
SET_DAILY_DATA,
|
||||
} = NODE_MUTATIONS;
|
||||
|
||||
const {
|
||||
GET_NODE_INFO,
|
||||
} = NODE_ACTIONS;
|
||||
|
||||
const statusThreshHoldMinutes = 120;
|
||||
const snoAPI = new SNOApi();
|
||||
|
||||
@ -90,9 +86,7 @@ export const node = {
|
||||
state.utilization.diskSpace.available = nodeInfo.diskSpace.available;
|
||||
state.utilization.bandwidth.used = nodeInfo.bandwidth.used;
|
||||
|
||||
state.disqualifiedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => {
|
||||
return satellite.disqualified;
|
||||
});
|
||||
state.disqualifiedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => satellite.disqualified);
|
||||
|
||||
state.satellites = nodeInfo.satellites || [];
|
||||
|
||||
@ -143,7 +137,7 @@ export const node = {
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
[GET_NODE_INFO]: async function ({commit}: any): Promise<void> {
|
||||
[NODE_ACTIONS.GET_NODE_INFO]: async function ({commit}: any): Promise<void> {
|
||||
const response = await snoAPI.dashboard();
|
||||
|
||||
commit(NODE_MUTATIONS.POPULATE_STORE, response);
|
||||
|
100
web/storagenode/src/app/store/modules/payout.ts
Normal file
100
web/storagenode/src/app/store/modules/payout.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import {
|
||||
HeldInfo,
|
||||
PaymentInfoParameters,
|
||||
PayoutApi,
|
||||
PayoutInfoRange, PayoutPeriod,
|
||||
PayoutState, TotalPayoutInfo,
|
||||
} from '@/app/types/payout';
|
||||
|
||||
export const PAYOUT_MUTATIONS = {
|
||||
SET_HELD_INFO: 'SET_HELD_INFO',
|
||||
SET_RANGE: 'SET_RANGE',
|
||||
SET_TOTAL: 'SET_TOTAL',
|
||||
};
|
||||
|
||||
export const PAYOUT_ACTIONS = {
|
||||
GET_HELD_INFO: 'GET_HELD_INFO',
|
||||
SET_PERIODS_RANGE: 'SET_PERIODS_RANGE',
|
||||
GET_TOTAL: 'GET_TOTAL',
|
||||
};
|
||||
|
||||
/**
|
||||
* creates notifications module with all dependencies
|
||||
*
|
||||
* @param api - payments api
|
||||
*/
|
||||
export function makePayoutModule(api: PayoutApi) {
|
||||
return {
|
||||
state: new PayoutState(),
|
||||
mutations: {
|
||||
[PAYOUT_MUTATIONS.SET_HELD_INFO](state: PayoutState, heldInfo: HeldInfo): void {
|
||||
state.heldInfo = heldInfo;
|
||||
},
|
||||
[PAYOUT_MUTATIONS.SET_TOTAL](state: PayoutState, totalPayoutInfo: TotalPayoutInfo): void {
|
||||
state.totalEarnings = totalPayoutInfo.totalEarnings;
|
||||
state.totalHeldAmount = totalPayoutInfo.totalHeldAmount;
|
||||
},
|
||||
[PAYOUT_MUTATIONS.SET_RANGE](state: PayoutState, periodRange: PayoutInfoRange): void {
|
||||
state.periodRange = periodRange;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
[PAYOUT_ACTIONS.GET_HELD_INFO]: async function ({commit, state, rootState}: any, satelliteId: string = ''): Promise<void> {
|
||||
const heldInfo = await api.getHeldInfo(new PaymentInfoParameters(
|
||||
state.periodRange.start,
|
||||
state.periodRange.end,
|
||||
satelliteId,
|
||||
));
|
||||
|
||||
heldInfo.surgePercent = getSurgePercentage(rootState.node.info.startedAt);
|
||||
|
||||
commit(PAYOUT_MUTATIONS.SET_HELD_INFO, heldInfo);
|
||||
},
|
||||
[PAYOUT_ACTIONS.GET_TOTAL]: async function ({commit, rootState}: any, satelliteId: string = ''): Promise<void> {
|
||||
const now = new Date();
|
||||
const totalPayoutInfo = await api.getTotal(new PaymentInfoParameters(
|
||||
new PayoutPeriod(rootState.node.info.startedAt.getUTCFullYear(), rootState.node.info.startedAt.getUTCMonth()),
|
||||
new PayoutPeriod(now.getUTCFullYear(), now.getUTCMonth()),
|
||||
satelliteId,
|
||||
));
|
||||
|
||||
commit(PAYOUT_MUTATIONS.SET_TOTAL, totalPayoutInfo);
|
||||
},
|
||||
[PAYOUT_ACTIONS.SET_PERIODS_RANGE]: function ({commit}: any, periodRange: PayoutInfoRange): void {
|
||||
commit(PAYOUT_MUTATIONS.SET_RANGE, periodRange);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
totalPeriodPayout: function (state: PayoutState): number {
|
||||
return state.heldInfo.compAtRest + state.heldInfo.compGet + state.heldInfo.compGetAudit
|
||||
+ state.heldInfo.compGetRepair - state.heldInfo.held;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns held percentage depends on number of months that node is online.
|
||||
* @param startedAt date since node is online.
|
||||
*/
|
||||
function getSurgePercentage(startedAt: Date): number {
|
||||
const now = new Date();
|
||||
const secondsInMonthApproximately = 2628000;
|
||||
const differenceInSeconds = (now.getTime() - startedAt.getTime()) / 1000;
|
||||
|
||||
const monthsOnline = Math.ceil(differenceInSeconds / secondsInMonthApproximately);
|
||||
|
||||
switch (true) {
|
||||
case monthsOnline < 4:
|
||||
return 75;
|
||||
case monthsOnline < 7:
|
||||
return 50;
|
||||
case monthsOnline < 10:
|
||||
return 25;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
101
web/storagenode/src/app/types/payout.ts
Normal file
101
web/storagenode/src/app/types/payout.ts
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Holds request arguments for payout information.
|
||||
*/
|
||||
export class PaymentInfoParameters {
|
||||
public constructor(
|
||||
public start: PayoutPeriod | null = null,
|
||||
public end: PayoutPeriod = new PayoutPeriod(),
|
||||
public satelliteId: string = '',
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds payout information.
|
||||
*/
|
||||
export class HeldInfo {
|
||||
public constructor(
|
||||
public usageAtRest: number = 0,
|
||||
public usageGet: number = 0,
|
||||
public usagePut: number = 0,
|
||||
public usageGetRepair: number = 0,
|
||||
public usagePutRepair: number = 0,
|
||||
public usageGetAudit: number = 0,
|
||||
public compAtRest: number = 0,
|
||||
public compGet: number = 0,
|
||||
public compPut: number = 0,
|
||||
public compGetRepair: number = 0,
|
||||
public compPutRepair: number = 0,
|
||||
public compGetAudit: number = 0,
|
||||
public surgePercent: number = 0,
|
||||
public held: number = 0,
|
||||
public owed: number = 0,
|
||||
public disposed: number = 0,
|
||||
public paid: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents payout period month and year.
|
||||
*/
|
||||
export class PayoutPeriod {
|
||||
public constructor(
|
||||
public year: number = new Date().getUTCFullYear(),
|
||||
public month: number = new Date().getUTCMonth(),
|
||||
) {}
|
||||
|
||||
public get period(): string {
|
||||
return this.month < 9 ? `${this.year}-0${this.month + 1}` : `${this.year}-${this.month + 1}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds 'start' and 'end' of payout period range.
|
||||
*/
|
||||
export class PayoutInfoRange {
|
||||
public constructor(
|
||||
public start: PayoutPeriod | null = null,
|
||||
public end: PayoutPeriod = new PayoutPeriod(),
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds accumulated held and earned payouts.
|
||||
*/
|
||||
export class TotalPayoutInfo {
|
||||
public constructor(
|
||||
public totalHeldAmount: number = 0,
|
||||
public totalEarnings: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all payout module state.
|
||||
*/
|
||||
export class PayoutState {
|
||||
public constructor (
|
||||
public heldInfo: HeldInfo = new HeldInfo(),
|
||||
public periodRange: PayoutInfoRange = new PayoutInfoRange(),
|
||||
public totalHeldAmount: number = 0,
|
||||
public totalEarnings: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes all payout-related functionality.
|
||||
*/
|
||||
export interface PayoutApi {
|
||||
/**
|
||||
* Fetches held amount information.
|
||||
* @throws Error
|
||||
*/
|
||||
getHeldInfo(paymentInfoParameters: PaymentInfoParameters): Promise<HeldInfo>;
|
||||
|
||||
/**
|
||||
* Fetches total payout information.
|
||||
* @throws Error
|
||||
*/
|
||||
getTotal(paymentInfoParameters: PaymentInfoParameters): Promise<TotalPayoutInfo>;
|
||||
}
|
@ -21,14 +21,14 @@ export function formatBytes(bytes): string {
|
||||
|
||||
switch (true) {
|
||||
case _bytes < MB:
|
||||
return `${(bytes / KB).toFixed(decimals)}KB`;
|
||||
return `${parseFloat((bytes / KB).toFixed(decimals))}KB`;
|
||||
case _bytes < GB:
|
||||
return `${(bytes / MB).toFixed(decimals)}MB`;
|
||||
return `${parseFloat((bytes / MB).toFixed(decimals))}MB`;
|
||||
case _bytes < TB:
|
||||
return `${(bytes / GB).toFixed(decimals)}GB`;
|
||||
return `${parseFloat((bytes / GB).toFixed(decimals))}GB`;
|
||||
case _bytes < PB:
|
||||
return `${(bytes / TB).toFixed(decimals)}TB`;
|
||||
return `${parseFloat((bytes / TB).toFixed(decimals))}TB`;
|
||||
default:
|
||||
return `${(bytes / PB).toFixed(decimals)}PB`;
|
||||
return `${parseFloat((bytes / PB).toFixed(decimals))}PB`;
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,9 @@ import SNOContentTitle from '@/app/components/SNOContentTitle.vue';
|
||||
|
||||
import { NODE_ACTIONS } from '@/app/store/modules/node';
|
||||
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { NotificationsCursor } from '@/app/types/notifications';
|
||||
|
||||
const {
|
||||
SELECT_SATELLITE,
|
||||
} = NODE_ACTIONS;
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
SNOContentTitle,
|
||||
@ -31,10 +28,12 @@ const {
|
||||
},
|
||||
})
|
||||
export default class Dashboard extends Vue {
|
||||
public mounted() {
|
||||
public async mounted(): Promise<void> {
|
||||
try {
|
||||
this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
this.$store.dispatch(SELECT_SATELLITE, null);
|
||||
await this.$store.dispatch(NOTIFICATIONS_ACTIONS.GET_NOTIFICATIONS, new NotificationsCursor(1));
|
||||
await this.$store.dispatch(NODE_ACTIONS.SELECT_SATELLITE, null);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -7,25 +7,34 @@
|
||||
<router-link to="/" class="payout-area-container__header__back-link">
|
||||
<BackArrowIcon />
|
||||
</router-link>
|
||||
<p class="payout-area-container__header__text">Payout information</p>
|
||||
<p class="payout-area-container__header__text">Payout Information</p>
|
||||
</header>
|
||||
<SatelliteSelection />
|
||||
<p class="payout-area-container__section-title">Payout</p>
|
||||
<EstimationArea class="payout-area-container__estimation" />
|
||||
<p class="payout-area-container__section-title">Held Amount</p>
|
||||
<p class="additional-text">Learn more about held back <a class="additional-text__link">here</a></p>
|
||||
<p class="additional-text">
|
||||
Learn more about held back
|
||||
<a
|
||||
class="additional-text__link"
|
||||
href="https://documentation.storj.io/resources/faq/held-back-amount"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
<section class="payout-area-container__held-info-area">
|
||||
<SingleInfo width="48%" label="Held Amount Rate" value="25%" />
|
||||
<SingleInfo v-if="false" width="48%" label="Total Held Amount" value="$19.93" />
|
||||
</section>
|
||||
<HeldProgress class="payout-area-container__process-area" />
|
||||
<section class="payout-area-container__held-history-container">
|
||||
<div class="payout-area-container__held-history-container__header">
|
||||
<p class="payout-area-container__held-history-container__header__title">Held Amount history</p>
|
||||
</div>
|
||||
<div class="payout-area-container__held-history-container__divider"></div>
|
||||
<HeldHistoryTable />
|
||||
<SingleInfo v-if="selectedSatellite.id" width="48%" label="Held Amount Rate" :value="heldRate + '%'" />
|
||||
<SingleInfo width="48%" label="Total Held Amount" :value="totalHeld | centsToDollars" />
|
||||
</section>
|
||||
<HeldProgress v-if="selectedSatellite.id" class="payout-area-container__process-area" />
|
||||
<!-- <section class="payout-area-container__held-history-container">-->
|
||||
<!-- <div class="payout-area-container__held-history-container__header">-->
|
||||
<!-- <p class="payout-area-container__held-history-container__header__title">Held Amount history</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="payout-area-container__held-history-container__divider"></div>-->
|
||||
<!-- <HeldHistoryTable />-->
|
||||
<!-- </section>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -40,6 +49,9 @@ import SatelliteSelection from '@/app/components/SatelliteSelection.vue';
|
||||
|
||||
import BackArrowIcon from '@/../static/images/notifications/backArrow.svg';
|
||||
|
||||
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
|
||||
import { SatelliteInfo } from '@/storagenode/dashboard';
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
HeldProgress,
|
||||
@ -50,7 +62,36 @@ import BackArrowIcon from '@/../static/images/notifications/backArrow.svg';
|
||||
BackArrowIcon,
|
||||
},
|
||||
})
|
||||
export default class PayoutArea extends Vue {}
|
||||
export default class PayoutArea extends Vue {
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Fetches payout information.
|
||||
*/
|
||||
public async mounted(): Promise<any> {
|
||||
try {
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO, this.$store.state.node.selectedSatellite.id);
|
||||
await this.$store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public get totalHeld(): number {
|
||||
return this.$store.state.payoutModule.totalHeldAmount;
|
||||
}
|
||||
|
||||
public get heldRate(): number {
|
||||
return this.$store.state.payoutModule.heldInfo.surgePercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* selectedSatellite - current selected satellite from store.
|
||||
* @return SatelliteInfo - current selected satellite
|
||||
*/
|
||||
public get selectedSatellite(): SatelliteInfo {
|
||||
return this.$store.state.node.selectedSatellite;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -31,6 +31,13 @@ Vue.directive('click-outside', {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* centsToDollars is a Vue filter that converts amount of cents in dollars string.
|
||||
*/
|
||||
Vue.filter('centsToDollars', (cents: number): string => {
|
||||
return `$${(cents / 100).toFixed(2)}`;
|
||||
});
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
|
139
web/storagenode/src/storagenode/api/payout.ts
Normal file
139
web/storagenode/src/storagenode/api/payout.ts
Normal file
@ -0,0 +1,139 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { HeldInfo, PaymentInfoParameters, PayoutApi, TotalPayoutInfo } from '@/app/types/payout';
|
||||
import { HttpClient } from '@/storagenode/utils/httpClient';
|
||||
|
||||
/**
|
||||
* NotificationsHttpApi is a http implementation of Notifications API.
|
||||
* Exposes all notifications-related functionality
|
||||
*/
|
||||
export class PayoutHttpApi implements PayoutApi {
|
||||
private readonly client: HttpClient = new HttpClient();
|
||||
private readonly ROOT_PATH: string = '/api/heldamount';
|
||||
|
||||
/**
|
||||
* Fetch held amount information.
|
||||
*
|
||||
* @returns held amount information
|
||||
* @throws Error
|
||||
*/
|
||||
public async getHeldInfo(paymentInfoParameters: PaymentInfoParameters): Promise<HeldInfo> {
|
||||
let path = `${this.ROOT_PATH}/paystubs/`;
|
||||
|
||||
if (paymentInfoParameters.start) {
|
||||
path += paymentInfoParameters.start.period + '/';
|
||||
}
|
||||
|
||||
path += paymentInfoParameters.end.period;
|
||||
|
||||
if (paymentInfoParameters.satelliteId) {
|
||||
path += '?id=' + paymentInfoParameters.satelliteId;
|
||||
}
|
||||
|
||||
const response = await this.client.get(path);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('can not get held information');
|
||||
}
|
||||
|
||||
const data: any[] = await response.json() || [];
|
||||
|
||||
let usageAtRest: number = 0;
|
||||
let usageGet: number = 0;
|
||||
let usagePut: number = 0;
|
||||
let usageGetRepair: number = 0;
|
||||
let usagePutRepair: number = 0;
|
||||
let usageGetAudit: number = 0;
|
||||
let compAtRest: number = 0;
|
||||
let compGet: number = 0;
|
||||
let compPut: number = 0;
|
||||
let compGetRepair: number = 0;
|
||||
let compPutRepair: number = 0;
|
||||
let compGetAudit: number = 0;
|
||||
let held: number = 0;
|
||||
let owed: number = 0;
|
||||
let disposed: number = 0;
|
||||
let paid: number = 0;
|
||||
|
||||
data.forEach((paystub: any) => {
|
||||
usageAtRest += paystub.usageAtRest;
|
||||
usageGet += paystub.usageGet;
|
||||
usagePut += paystub.usagePut;
|
||||
usageGetRepair += paystub.usageGetRepair;
|
||||
usagePutRepair += paystub.usagePutRepair;
|
||||
usageGetAudit += paystub.usageGetAudit;
|
||||
compAtRest += paystub.compAtRest;
|
||||
compGet += paystub.compGet;
|
||||
compPut += paystub.compPut;
|
||||
compGetRepair += paystub.compGetRepair;
|
||||
compPutRepair += paystub.compPutRepair;
|
||||
compGetAudit += paystub.compGetAudit;
|
||||
held += paystub.held;
|
||||
owed += paystub.owed;
|
||||
disposed += paystub.disposed;
|
||||
paid += paystub.paid;
|
||||
});
|
||||
|
||||
return new HeldInfo(
|
||||
usageAtRest,
|
||||
usageGet,
|
||||
usagePut,
|
||||
usageGetRepair,
|
||||
usagePutRepair,
|
||||
usageGetAudit,
|
||||
compAtRest,
|
||||
compGet,
|
||||
compPut,
|
||||
compGetRepair,
|
||||
compPutRepair,
|
||||
compGetAudit,
|
||||
0,
|
||||
held,
|
||||
owed,
|
||||
disposed,
|
||||
paid,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch total payout information.
|
||||
*
|
||||
* @returns total payout information
|
||||
* @throws Error
|
||||
*/
|
||||
public async getTotal(paymentInfoParameters: PaymentInfoParameters): Promise<TotalPayoutInfo> {
|
||||
let path = `${this.ROOT_PATH}/paystubs/`;
|
||||
|
||||
if (paymentInfoParameters.start) {
|
||||
path += paymentInfoParameters.start.period + '/';
|
||||
}
|
||||
|
||||
path += paymentInfoParameters.end.period;
|
||||
|
||||
if (paymentInfoParameters.satelliteId) {
|
||||
path += '?id=' + paymentInfoParameters.satelliteId;
|
||||
}
|
||||
|
||||
const response = await this.client.get(path);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('can not get total payout information');
|
||||
}
|
||||
|
||||
const data: any[] = await response.json() || [];
|
||||
|
||||
let held: number = 0;
|
||||
let paid: number = 0;
|
||||
|
||||
data.forEach((paystub: any) => {
|
||||
held += paystub.held;
|
||||
paid += paystub.paid;
|
||||
});
|
||||
|
||||
return new TotalPayoutInfo(
|
||||
held,
|
||||
paid,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user