generated from nathanwoodburn/python-webserver-template
feat: Add new api routes and updated chart
All checks were successful
Build Docker / BuildImage (push) Successful in 34s
All checks were successful
Build Docker / BuildImage (push) Successful in 34s
This commit is contained in:
parent
e995df2e05
commit
7aebf2b92f
2
cache/10118a51009b13b2592c87579e15e61e.json
vendored
2
cache/10118a51009b13b2592c87579e15e61e.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367496.996954, "result": "120"}
|
{"timestamp": 1733371180.803243, "result": "120"}
|
2
cache/1ccff5c6f117409fea0c861aa44b8e62.json
vendored
2
cache/1ccff5c6f117409fea0c861aa44b8e62.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367305.82001, "result": 1.18}
|
{"timestamp": 1733370947.4475856, "result": 1.19}
|
2
cache/29409a8a40dd2d547a7a44b8f6758f54.json
vendored
2
cache/29409a8a40dd2d547a7a44b8f6758f54.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367305.1029575, "result": 4.18}
|
{"timestamp": 1733370946.7141268, "result": 4.2}
|
2
cache/4104ed0427efe63d4ca0dead970a4391.json
vendored
2
cache/4104ed0427efe63d4ca0dead970a4391.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367303.484976, "result": 248.08}
|
{"timestamp": 1733370945.6919894, "result": 249.75}
|
2
cache/598f5dbf97fb0d45cbc6e1a5b0a3b575.json
vendored
2
cache/598f5dbf97fb0d45cbc6e1a5b0a3b575.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367300.1864865, "result": 231.97}
|
{"timestamp": 1733370941.0851188, "result": 234.61}
|
2
cache/6eec370e2713cfc84c84e1080b8a191a.json
vendored
2
cache/6eec370e2713cfc84c84e1080b8a191a.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367298.2447531, "result": 120.0}
|
{"timestamp": 1733370937.8074176, "result": 120.0}
|
2
cache/790b28a9a21cf694ad9577ef1072ac9e.json
vendored
2
cache/790b28a9a21cf694ad9577ef1072ac9e.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733365705.1888373, "result": 0.006517062}
|
{"timestamp": 1733370648.2231722, "result": 0.006517062}
|
2
cache/94ac30c93587c50252ac382a8d02257f.json
vendored
2
cache/94ac30c93587c50252ac382a8d02257f.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367300.1870453, "result": 1.5117628721399998}
|
{"timestamp": 1733370941.0857272, "result": 1.52896791582}
|
2
cache/a071d7bdda25c22e42ad7840f17c4b0e.json
vendored
2
cache/a071d7bdda25c22e42ad7840f17c4b0e.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367301.6211267, "result": 1.001}
|
{"timestamp": 1733370944.0799723, "result": 1.001}
|
2
cache/a0ee60913ba556f39d128e7d7249e788.json
vendored
2
cache/a0ee60913ba556f39d128e7d7249e788.json
vendored
@ -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"}]}
|
2
cache/b81351778df9f812bbd75ee85a7a073e.json
vendored
2
cache/b81351778df9f812bbd75ee85a7a073e.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367305.8206198, "result": 119.1529266475}
|
{"timestamp": 1733370947.4478178, "result": 120.11282257281998}
|
2
cache/ccf2a009e56f1b05d471a55d9c9ea8ea.json
vendored
2
cache/ccf2a009e56f1b05d471a55d9c9ea8ea.json
vendored
@ -1 +1 @@
|
|||||||
{"timestamp": 1733367305.512328, "result": 82.815227}
|
{"timestamp": 1733370947.155014, "result": 82.815227}
|
67
server.py
67
server.py
@ -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
|
||||||
|
BIN
stWDBRN.bsdesign
BIN
stWDBRN.bsdesign
Binary file not shown.
7
templates/assets/js/bs-init.js
Normal file
7
templates/assets/js/bs-init.js
Normal 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);
|
@ -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');
|
pieSliceText: 'label',
|
||||||
data.addColumn('number', 'Value');
|
tooltip: { isHtml: false },
|
||||||
data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
|
legend: {
|
||||||
|
position: 'right',
|
||||||
data.addRows([
|
textStyle: { color: 'white' }
|
||||||
{% for token, value, name in pie_chart %}
|
},
|
||||||
['{{ token }}', {{ value }}, '{{name}}'],
|
backgroundColor: '#212529',
|
||||||
{% endfor %}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Set chart options
|
|
||||||
var options = {
|
|
||||||
pieSliceText: 'label',
|
|
||||||
legend: {
|
|
||||||
position: 'right',
|
|
||||||
textStyle: {
|
|
||||||
color: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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
|
|
||||||
if (width < 700) {
|
|
||||||
chart.draw(data, options);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
chart.draw(data, {
|
|
||||||
...options,
|
|
||||||
width: width,
|
|
||||||
height: height
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeChart();
|
|
||||||
|
|
||||||
window.addEventListener('resize', resizeChart);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script></div><a class="btn btn-secondary" role="button" href="https://www.coingecko.com/en/portfolios/public/vault" target="_blank">View on Coingecko</a>
|
|
||||||
|
async function fetchData(apiUrl) {
|
||||||
|
try {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformDataForChart(data) {
|
||||||
|
const chartData = [];
|
||||||
|
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();
|
||||||
|
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" 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>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user