feat: Add new api routes and updated chart
All checks were successful
Build Docker / BuildImage (push) Successful in 34s

This commit is contained in:
Nathan Woodburn 2024-12-05 15:35:09 +11:00
parent e995df2e05
commit 7aebf2b92f
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
16 changed files with 248 additions and 71 deletions

View File

@ -1 +1 @@
{"timestamp": 1733367496.996954, "result": "120"} {"timestamp": 1733371180.803243, "result": "120"}

View File

@ -1 +1 @@
{"timestamp": 1733367305.82001, "result": 1.18} {"timestamp": 1733370947.4475856, "result": 1.19}

View File

@ -1 +1 @@
{"timestamp": 1733367305.1029575, "result": 4.18} {"timestamp": 1733370946.7141268, "result": 4.2}

View File

@ -1 +1 @@
{"timestamp": 1733367303.484976, "result": 248.08} {"timestamp": 1733370945.6919894, "result": 249.75}

View File

@ -1 +1 @@
{"timestamp": 1733367300.1864865, "result": 231.97} {"timestamp": 1733370941.0851188, "result": 234.61}

View File

@ -1 +1 @@
{"timestamp": 1733367298.2447531, "result": 120.0} {"timestamp": 1733370937.8074176, "result": 120.0}

View File

@ -1 +1 @@
{"timestamp": 1733365705.1888373, "result": 0.006517062} {"timestamp": 1733370648.2231722, "result": 0.006517062}

View File

@ -1 +1 @@
{"timestamp": 1733367300.1870453, "result": 1.5117628721399998} {"timestamp": 1733370941.0857272, "result": 1.52896791582}

View File

@ -1 +1 @@
{"timestamp": 1733367301.6211267, "result": 1.001} {"timestamp": 1733370944.0799723, "result": 1.001}

View File

@ -1 +1 @@
{"timestamp": 1733367305.106161, "result": [{"mint": "jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v", "balance": 0.039815492, "price": 248.08, "value": 9.87742725536, "name": "Jupiter Staked SOL", "symbol": "jupsol"}, {"mint": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", "balance": 2.402337, "price": 4.18, "value": 10.04176866, "name": "Jupiter Perpetuals Liquidity Provider Token", "symbol": "jlp"}]} {"timestamp": 1733370946.7175624, "result": [{"mint": "jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v", "balance": 0.039815492, "price": 249.75, "value": 9.943919127000001, "name": "Jupiter Staked SOL", "symbol": "jupsol"}, {"mint": "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", "balance": 2.402337, "price": 4.2, "value": 10.0898154, "name": "Jupiter Perpetuals Liquidity Provider Token", "symbol": "jlp"}]}

View File

@ -1 +1 @@
{"timestamp": 1733367305.8206198, "result": 119.1529266475} {"timestamp": 1733370947.4478178, "result": 120.11282257281998}

View File

@ -1 +1 @@
{"timestamp": 1733367305.512328, "result": 82.815227} {"timestamp": 1733370947.155014, "result": 82.815227}

View File

@ -124,7 +124,7 @@ def index():
pie_chart_data.append( pie_chart_data.append(
("ADA", cardanoBalance, f"Cardano: ${cardanoBalance}")) ("ADA", cardanoBalance, f"Cardano: ${cardanoBalance}"))
return render_template("index.html", value=tokenValue, supply=tokenSupply, pie_chart=pie_chart_data, vault=vaultBalance) return render_template("index.html", value=tokenValue, supply=tokenSupply, vault=vaultBalance)
@app.route("/<path:path>") @app.route("/<path:path>")
@ -334,7 +334,7 @@ def api_token():
token[t["symbol"].upper()]["amount"] = t["balance"] / supply token[t["symbol"].upper()]["amount"] = t["balance"] / supply
token[t["symbol"].upper()]["value"] = t["price"] * t["balance"] / supply token[t["symbol"].upper()]["value"] = t["price"] * t["balance"] / supply
# Round value to 4 decimal places # Round value to 4 decimal places
token[t["symbol"].upper()]["value"] = round(token[t["symbol"].upper()]["value"], 4) token[t["symbol"].upper()]["value"] = round(token[t["symbol"].upper()]["value"], 2)
token[t["symbol"].upper()]["amount"] = round(token[t["symbol"].upper()]["amount"], 4) token[t["symbol"].upper()]["amount"] = round(token[t["symbol"].upper()]["amount"], 4)
# Remove balance key # Remove balance key
@ -344,13 +344,18 @@ def api_token():
token["ADA"] = { token["ADA"] = {
"name": "Cardano", "name": "Cardano",
"amount": getCardanoBalance(vault_cardano_address) / supply, "amount": round(getCardanoBalance(vault_cardano_address) / supply,4),
"value": getCardanoValue(vault_cardano_address) / supply "value": round(getCardanoValue(vault_cardano_address) / supply,2)
} }
if token["ADA"]["value"] < 0.01: if token["ADA"]["value"] < 0.01:
token["ADA"]["amount"] = 0 token["ADA"]["amount"] = 0
token["ADA"]["value"] = 0 token["ADA"]["value"] = 0
# For each key add tooltip
for key in token:
token[key]["tooltip"] = f"{token[key]['amount']} {key} (${token[key]['value']})"
token["total"] = { token["total"] = {
"name": "stWDBRN", "name": "stWDBRN",
"description": "stWDBRN total value (USD)", "description": "stWDBRN total value (USD)",
@ -361,6 +366,60 @@ def api_token():
return jsonify(token) return jsonify(token)
@app.route("/api/v1/vault")
def api_vault():
tokens = getTokens()
vaultBalance = getVaultBalance()
vaultBalance = "{:.2f}".format(vaultBalance)
vault = {}
vault["SOL"] = {
"name": "Solana",
"amount": round(getSolBalance(),4),
"value": round(getSolValue(),2)
}
if vault["SOL"]["value"] < 0.01:
vault["SOL"]["amount"] = 0
vault["SOL"]["value"] = 0
vault["ADA"] = {
"name": "Cardano",
"amount": round(getCardanoBalance(vault_cardano_address),4),
"value": round(getCardanoValue(vault_cardano_address),2)
}
if vault["ADA"]["value"] < 0.01:
vault["ADA"]["amount"] = 0
vault["ADA"]["value"] = 0
for t in tokens:
vault[t["symbol"].upper()] = t
if vault[t["symbol"].upper()]["value"] < 0.01:
vault[t["symbol"].upper()]["amount"] = 0
vault[t["symbol"].upper()]["value"] = 0
else:
vault[t["symbol"].upper()]["amount"] = t["balance"]
vault[t["symbol"].upper()]["value"] = t["price"] * t["balance"]
# Round value to 4 decimal places
vault[t["symbol"].upper()]["value"] = round(vault[t["symbol"].upper()]["value"], 2)
vault[t["symbol"].upper()]["amount"] = round(vault[t["symbol"].upper()]["amount"], 4)
# Remove balance key
del vault[t["symbol"].upper()]["balance"]
# For each key add tooltip
for key in vault:
vault[key]["tooltip"] = f"{vault[key]['amount']} {key} (${vault[key]['value']})"
vault["total"] = {
"name": "Vault",
"description": "Total Vault value (USD)",
"value": vaultBalance
}
return jsonify(vault)
# endregion # endregion

Binary file not shown.

View File

@ -0,0 +1,7 @@
document.addEventListener('DOMContentLoaded', function() {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bss-tooltip]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
})
}, false);

View File

@ -47,68 +47,178 @@
</header> </header>
<section style="margin-top: 50px;margin-bottom: 50px;max-width: 100vw;overflow: hidden;"> <section style="margin-top: 50px;margin-bottom: 50px;max-width: 100vw;overflow: hidden;">
<div class="text-center"> <div class="text-center">
<h1>Current Vault Contents</h1> <h1 data-bs-toggle="tooltip" data-bss-tooltip="" id="chart-header" title="Toggle per token view" style="border-bottom: 5px dashed var(--bs-body-color);width: fit-content;margin: auto;margin-bottom: 20px;">Current Vault Contents</h1>
<div class="text-center" id="data-table"></div>
<div id="pie-chart" style="margin: auto;"><script type="text/javascript"> <div id="pie-chart" style="margin: auto;"><script type="text/javascript">
window.onload = function () { window.onload = function () {
google.charts.load('current', { google.charts.load('current', { packages: ['corechart'] });
packages: ['corechart']
});
// Set a callback to run when the API is loaded. // Set a callback to run when the API is loaded.
google.charts.setOnLoadCallback(drawChart); google.charts.setOnLoadCallback(drawChart);
function drawChart() { let isPerTokenView = false; // Track the current view state
// Create data array from Flask data passed into the template let chart; // Declare the chart instance globally to reuse it
var data = new google.visualization.DataTable(); let options = {
data.addColumn('string', 'Token');
data.addColumn('number', 'Value');
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
data.addRows([
{% for token, value, name in pie_chart %}
['{{ token }}', {{ value }}, '{{name}}'],
{% endfor %}
]);
// Set chart options
var options = {
pieSliceText: 'label', pieSliceText: 'label',
tooltip: { isHtml: false },
legend: { legend: {
position: 'right', position: 'right',
textStyle: { textStyle: { color: 'white' }
color: 'white'
}
}, },
backgroundColor: '#212529', backgroundColor: '#212529',
}; };
var chart = new google.visualization.PieChart(document.getElementById('pie-chart'));
function resizeChart() {
// Dynamically adjust the chart size to the container
var width = window.innerWidth * 0.8;
var height = window.innerHeight * 0.5;
// If the width is less than 600 px use full width async function fetchData(apiUrl) {
if (width < 700) { try {
chart.draw(data, options); const response = await fetch(apiUrl);
if (!response.ok) throw new Error('Failed to fetch data from API');
return await response.json();
} catch (error) {
console.error('Error fetching data:', error);
return null;
} }
else { }
chart.draw(data, {
...options, function transformDataForChart(data) {
width: width, const chartData = [];
height: height for (const token in data) {
if (token !== 'total') {
chartData.push([
token, // Label for the chart
data[token].value, // Value for the chart
data[token].tooltip, // Tooltip for the chart
]);
}
}
return chartData;
}
async function drawChart() {
const apiUrl = '/api/v1/vault';
const data = await fetchData(apiUrl);
if (!data) return; // Exit if data fetch fails
const chartDataTable = new google.visualization.DataTable();
chartDataTable.addColumn('string', 'Token');
chartDataTable.addColumn('number', 'Value');
chartDataTable.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': false } });
chartDataTable.addRows(transformDataForChart(data));
chart = new google.visualization.PieChart(document.getElementById('pie-chart'));
resizeAndDraw(chart, chartDataTable, options);
updateText(true);
populateTable(data);
}
function updateText(isVault) {
let headerText = document.getElementById('chart-header');
if (isVault) {
headerText.innerText = 'Current Vault Contents';
} else {
headerText.innerText = 'Current Per Token Holding';
}
}
function populateTable(data) {
const tableContainer = document.getElementById('data-table');
tableContainer.innerHTML = ''; // Clear previous table data
// Create table elements
const table = document.createElement('table');
table.style.margin = 'auto';
table.style.borderCollapse = 'collapse';
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// Create table header
const headerRow = document.createElement('tr');
['Name', 'Amount', 'Value'].forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
th.style.border = '1px solid #ccc';
th.style.padding = '8px 20px';
th.style.backgroundColor = '#333';
th.style.color = 'white';
headerRow.appendChild(th);
}); });
thead.appendChild(headerRow);
// Create table rows
for (const token in data) {
if (token !== 'total') {
const row = document.createElement('tr');
const nameCell = document.createElement('td');
nameCell.textContent = data[token].name;
nameCell.style.border = '1px solid #ccc';
nameCell.style.padding = '8px 20px';
const amountCell = document.createElement('td');
amountCell.textContent = `${data[token].amount} ${token}`;
amountCell.style.border = '1px solid #ccc';
amountCell.style.padding = '8px 20px';
const valueCell = document.createElement('td');
valueCell.textContent = data[token].value;
valueCell.style.border = '1px solid #ccc';
valueCell.style.padding = '8px 20px';
row.appendChild(nameCell);
row.appendChild(amountCell);
row.appendChild(valueCell);
tbody.appendChild(row);
} }
} }
table.appendChild(thead);
table.appendChild(tbody);
tableContainer.appendChild(table);
}
async function toggleChart() {
isPerTokenView = !isPerTokenView;
const apiUrl = isPerTokenView ? '/api/v1/token' : '/api/v1/vault';
const data = await fetchData(apiUrl);
if (!data) return; // Exit if data fetch fails
const chartDataTable = new google.visualization.DataTable();
chartDataTable.addColumn('string', 'Token');
chartDataTable.addColumn('number', 'Value');
chartDataTable.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': false } });
chartDataTable.addRows(transformDataForChart(data));
resizeAndDraw(chart, chartDataTable, options);
updateText(!isPerTokenView);
populateTable(data);
}
function resizeAndDraw(chart, chartDataTable, options) {
function resizeChart() {
const width = window.innerWidth * 0.8;
const height = window.innerHeight * 0.5;
const dynamicOptions = { ...options };
if (width >= 700) {
dynamicOptions.width = width;
dynamicOptions.height = height;
}
chart.draw(chartDataTable, dynamicOptions);
}
resizeChart(); resizeChart();
window.addEventListener('resize', resizeChart); window.addEventListener('resize', resizeChart);
} }
// Set button event listener
document.getElementById('chart-header').addEventListener('click', toggleChart);
}; };
</script></div><a class="btn btn-secondary" role="button" href="https://www.coingecko.com/en/portfolios/public/vault" target="_blank">View on Coingecko</a> </script>
</div><a class="btn btn-secondary" role="button" href="https://www.coingecko.com/en/portfolios/public/vault" target="_blank" style="margin: 10px;">View on Coingecko</a>
</div> </div>
</section> </section>
<section> <section>
@ -189,6 +299,7 @@
</div> </div>
</footer> </footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script> <script src="https://www.gstatic.com/charts/loader.js"></script>
</body> </body>