Skip to content

Commit 5488979

Browse files
committed
Rentals in Billing Center
1 parent 082ec0b commit 5488979

File tree

4 files changed

+465
-0
lines changed

4 files changed

+465
-0
lines changed

services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
3232

3333
if (osparc.data.Permissions.getInstance().canDo("usage.all.read")) {
3434
this.__usagePage = this.__addUsagePage();
35+
this.__rentalsPage = this.__addRentalsPage();
3536
}
3637
},
3738

@@ -59,6 +60,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
5960
__paymentMethodsPage: null,
6061
__transactionsPage: null,
6162
__usagePage: null,
63+
__rentalsPage: null,
6264
__paymentMethods: null,
6365
__transactionsTable: null,
6466

@@ -95,6 +97,14 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", {
9597
return page;
9698
},
9799

100+
__addRentalsPage: function() {
101+
const title = this.tr("Rentals");
102+
const iconSrc = "@FontAwesome5Solid/list/22";
103+
const rentals = new osparc.desktop.credits.Rentals();
104+
const page = this.addTab(title, iconSrc, rentals);
105+
return page;
106+
},
107+
98108
openWallets: function() {
99109
if (this.__walletsPage) {
100110
return this._openPage(this.__walletsPage);
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
19+
qx.Class.define("osparc.desktop.credits.Rentals", {
20+
extend: qx.ui.core.Widget,
21+
22+
construct: function() {
23+
this.base(arguments);
24+
25+
this._setLayout(new qx.ui.layout.VBox(15));
26+
27+
const store = osparc.store.Store.getInstance();
28+
this.__userWallets = store.getWallets();
29+
30+
this.__buildLayout()
31+
},
32+
33+
members: {
34+
__buildLayout: function() {
35+
this._removeAll();
36+
37+
const container = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
38+
39+
const lbl = new qx.ui.basic.Label("Select a Credit Account:");
40+
container.add(lbl);
41+
42+
const selectBoxContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
43+
const walletSelectBox = new qx.ui.form.SelectBox().set({
44+
allowStretchX: false,
45+
width: 200
46+
});
47+
selectBoxContainer.add(walletSelectBox);
48+
this.__fetchingImg = new qx.ui.basic.Image().set({
49+
source: "@FontAwesome5Solid/circle-notch/12",
50+
alignX: "center",
51+
alignY: "middle",
52+
visibility: "excluded"
53+
});
54+
this.__fetchingImg.getContentElement().addClass("rotate");
55+
selectBoxContainer.add(this.__fetchingImg);
56+
container.add(selectBoxContainer);
57+
58+
const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox(5))
59+
this.__dateFilters = new osparc.desktop.credits.DateFilters();
60+
this.__dateFilters.addListener("change", e => {
61+
this.__table.getTableModel().setFilters(e.getData())
62+
this.__table.getTableModel().reloadData()
63+
});
64+
filterContainer.add(this.__dateFilters);
65+
filterContainer.add(new qx.ui.core.Spacer(), {
66+
flex: 1
67+
});
68+
this.__exportButton = new qx.ui.form.Button(this.tr("Export")).set({
69+
allowStretchY: false,
70+
alignY: "bottom"
71+
});
72+
this.__exportButton.addListener("execute", () => {
73+
this.__handleExport()
74+
});
75+
filterContainer.add(this.__exportButton);
76+
const refreshButton = new qx.ui.form.Button(this.tr("Reload"), "@FontAwesome5Solid/sync-alt/14").set({
77+
allowStretchY: false,
78+
alignY: "bottom"
79+
});
80+
refreshButton.addListener("execute", () => this.__table && this.__table.getTableModel().reloadData());
81+
filterContainer.add(refreshButton)
82+
container.add(filterContainer);
83+
84+
this._add(container);
85+
86+
walletSelectBox.addListener("changeSelection", e => {
87+
const selection = e.getData();
88+
if (selection.length) {
89+
this.__selectedWallet = selection[0].getModel()
90+
if (this.__table) {
91+
this.__table.getTableModel().setWalletId(this.__selectedWallet.getWalletId())
92+
this.__table.getTableModel().reloadData()
93+
} else {
94+
// qx: changeSelection is triggered after the first item is added to SelectBox
95+
this.__table = new osparc.desktop.credits.RentalsTable(this.__selectedWallet.getWalletId(), this.__dateFilters.getValue()).set({
96+
marginTop: 10
97+
})
98+
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
99+
converter: isFetching => isFetching ? "visible" : "excluded"
100+
})
101+
container.add(this.__table, { flex: 1 })
102+
}
103+
}
104+
});
105+
106+
if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
107+
this.__userWallets.forEach(wallet => {
108+
walletSelectBox.add(new qx.ui.form.ListItem(wallet.getName(), null, wallet));
109+
});
110+
} else {
111+
lbl.setVisibility("excluded")
112+
walletSelectBox.setVisibility("excluded")
113+
this.__exportButton.setVisibility("excluded")
114+
this.__table = new osparc.desktop.credits.RentalsTable(null, this.__dateFilters.getValue()).set({
115+
marginTop: 10
116+
})
117+
this.__table.getTableModel().bind("isFetching", this.__fetchingImg, "visibility", {
118+
converter: isFetching => isFetching ? "visible" : "excluded"
119+
})
120+
container.add(this.__table, { flex: 1 })
121+
}
122+
},
123+
__handleExport() {
124+
const reportUrl = new URL("/v0/services/-/rentals-report", window.location.origin)
125+
reportUrl.searchParams.append("wallet_id", this.__selectedWallet.getWalletId())
126+
reportUrl.searchParams.append("filters", JSON.stringify({ "started_at": this.__dateFilters.getValue() }))
127+
window.open(reportUrl, "_blank")
128+
}
129+
}
130+
});
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
19+
qx.Class.define("osparc.desktop.credits.RentalsTable", {
20+
extend: qx.ui.table.Table,
21+
22+
construct: function(walletId, filters) {
23+
this.base(arguments)
24+
const model = new osparc.desktop.credits.RentalsTableModel(walletId, filters)
25+
this.setTableModel(model)
26+
this.setStatusBarVisible(false)
27+
28+
this.setHeaderCellHeight(26);
29+
this.setRowHeight(26);
30+
31+
const columnModel = this.getTableColumnModel();
32+
33+
columnModel.setDataCellRenderer(this.self().COLS.COST.column, new qx.ui.table.cellrenderer.Number());
34+
35+
if (!osparc.desktop.credits.Utils.areWalletsEnabled()) {
36+
columnModel.setColumnVisible(this.self().COLS.COST.column, false);
37+
columnModel.setColumnVisible(this.self().COLS.USER.column, false);
38+
}
39+
columnModel.setColumnVisible(this.self().COLS.SERVICE.column, false);
40+
41+
// Array [0, 1, ..., N] where N is column_count - 1 (default column order)
42+
this.__columnOrder = [...Array(columnModel.getOverallColumnCount()).keys()]
43+
44+
if (
45+
osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder() &&
46+
osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder().length === this.__columnOrder.length
47+
) {
48+
columnModel.setColumnsOrder(osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder())
49+
this.__columnOrder = osparc.Preferences.getInstance().getBillingCenterRentalsColumnOrder()
50+
} else {
51+
osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder)
52+
}
53+
54+
columnModel.addListener("orderChanged", e => {
55+
// Save new order into preferences
56+
if (e.getData()) {
57+
const { fromOverXPos, toOverXPos } = e.getData()
58+
// Edit current order
59+
this.__columnOrder = this.__columnOrder.toSpliced(toOverXPos, 0, this.__columnOrder.splice(fromOverXPos, 1)[0])
60+
// Save order
61+
osparc.Preferences.getInstance().setBillingCenterRentalsColumnOrder(this.__columnOrder)
62+
}
63+
}, this)
64+
65+
Object.values(this.self().COLS).forEach(col => columnModel.setColumnWidth(col.column, col.width));
66+
},
67+
68+
statics: {
69+
COLS: {
70+
PROJECT: {
71+
id: "project",
72+
column: 0,
73+
label: osparc.product.Utils.getStudyAlias({firstUpperCase: true}),
74+
width: 140
75+
},
76+
NODE: {
77+
id: "node",
78+
column: 1,
79+
label: qx.locale.Manager.tr("Node"),
80+
width: 140
81+
},
82+
SERVICE: {
83+
id: "service",
84+
column: 2,
85+
label: qx.locale.Manager.tr("Service"),
86+
width: 140
87+
},
88+
START: {
89+
id: "start",
90+
column: 3,
91+
label: qx.locale.Manager.tr("Start"),
92+
width: 130
93+
},
94+
DURATION: {
95+
id: "duration",
96+
column: 4,
97+
label: qx.locale.Manager.tr("Duration"),
98+
width: 70
99+
},
100+
STATUS: {
101+
id: "status",
102+
column: 5,
103+
label: qx.locale.Manager.tr("Status"),
104+
width: 70
105+
},
106+
COST: {
107+
id: "cost",
108+
column: 6,
109+
label: qx.locale.Manager.tr("Credits"),
110+
width: 56
111+
},
112+
USER: {
113+
id: "user",
114+
column: 7,
115+
label: qx.locale.Manager.tr("User"),
116+
width: 140
117+
},
118+
TAGS: {
119+
id: "tags",
120+
column: 7,
121+
label: qx.locale.Manager.tr("Tags"),
122+
width: 140
123+
},
124+
}
125+
}
126+
});

0 commit comments

Comments
 (0)