feat: Add some basic auction functions
This commit is contained in:
parent
6aeb02cdd2
commit
9e02fd7774
46
account.py
46
account.py
@ -204,3 +204,49 @@ def getDNS(domain: str):
|
||||
def getNodeSync():
|
||||
response = hsd.getInfo()
|
||||
return response['chain']['progress']*100
|
||||
|
||||
|
||||
def getBids(account, domain):
|
||||
response = hsw.getWalletBidsByName(domain,account)
|
||||
return response
|
||||
|
||||
def rescan_auction(account,domain):
|
||||
# Get height of the start of the auction
|
||||
response = hsw.rpc_selectWallet(account)
|
||||
|
||||
|
||||
response = hsd.rpc_getNameInfo(domain)
|
||||
if 'result' not in response:
|
||||
return {
|
||||
"error": "Invalid domain"
|
||||
}
|
||||
if 'bidPeriodStart' not in response['result']['info']['stats']:
|
||||
return {
|
||||
"error": "Not in auction"
|
||||
}
|
||||
height = response['result']['info']['stats']['bidPeriodStart']
|
||||
response = hsw.rpc_importName(domain,height)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def bid(account,domain,bid,blind):
|
||||
account_name = check_account(account)
|
||||
password = ":".join(account.split(":")[1:])
|
||||
|
||||
if account_name == False:
|
||||
return {
|
||||
"error": "Invalid account"
|
||||
}
|
||||
|
||||
bid = int(bid)*1000000
|
||||
lockup = int(blind)*1000000 + bid
|
||||
|
||||
try:
|
||||
response = hsw.sendBID(account_name,password,domain,bid,lockup)
|
||||
return response
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": str(e)
|
||||
}
|
||||
|
149
main.py
149
main.py
@ -195,7 +195,6 @@ def search():
|
||||
search_term=search_term,domain=search_term,
|
||||
state="AVAILABLE", next="Available Now")
|
||||
|
||||
|
||||
state = domain['info']['state']
|
||||
if state == 'CLOSED':
|
||||
if not domain['info']['registered']:
|
||||
@ -206,12 +205,10 @@ def search():
|
||||
expires = domain['info']['stats']['daysUntilExpire']
|
||||
next = f"Expires in ~{expires} days"
|
||||
elif state == 'OPENING':
|
||||
print(domain['info']['stats'])
|
||||
next = "Bidding opens in ~" + str(domain['info']['stats']['blocksUntilBidding']) + " blocks"
|
||||
elif state == 'BIDDING':
|
||||
next = "Reveal in ~" + str(domain['info']['stats']['blocksUntilReveal']) + " blocks"
|
||||
elif state == 'REVEAL':
|
||||
print(domain['info']['stats'])
|
||||
next = "Reveal ends in ~" + str(domain['info']['stats']['blocksUntilClose']) + " blocks"
|
||||
|
||||
|
||||
@ -285,6 +282,152 @@ def renew(domain):
|
||||
response = account_module.renewDomain(request.cookies.get("account"),domain)
|
||||
return redirect("/success?tx=" + response['hash'])
|
||||
|
||||
|
||||
|
||||
@app.route('/auction/<domain>')
|
||||
def auction(domain):
|
||||
# Check if the user is logged in
|
||||
if request.cookies.get("account") is None:
|
||||
return redirect("/login")
|
||||
|
||||
account = account_module.check_account(request.cookies.get("account"))
|
||||
if not account:
|
||||
return redirect("/logout")
|
||||
|
||||
search_term = domain.lower().strip()
|
||||
# Convert emoji to punycode
|
||||
search_term = domainLookup.emoji_to_punycode(search_term)
|
||||
if len(search_term) == 0:
|
||||
return redirect("/")
|
||||
|
||||
domainInfo = account_module.getDomain(search_term)
|
||||
|
||||
if 'error' in domainInfo:
|
||||
return render_template("auction.html", account=account,sync=account_module.getNodeSync(),
|
||||
search_term=search_term, domain=domainInfo['error'])
|
||||
|
||||
if domainInfo['info'] is None:
|
||||
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
|
||||
return render_template("auction.html", account=account, sync=account_module.getNodeSync(),
|
||||
search_term=search_term,domain=search_term,next_action=next_action,
|
||||
state="AVAILABLE", next="Open Auction")
|
||||
|
||||
state = domainInfo['info']['state']
|
||||
next_action = ''
|
||||
|
||||
bids = account_module.getBids(account,search_term)
|
||||
if bids == []:
|
||||
bids = "No bids found"
|
||||
next_action = f'<a href="/auction/{domain}/scan">Rescan Auction</a>'
|
||||
else:
|
||||
bids = render.bids(bids)
|
||||
|
||||
|
||||
if state == 'CLOSED':
|
||||
if not domainInfo['info']['registered']:
|
||||
state = 'AVAILABLE'
|
||||
next = "Available Now"
|
||||
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
|
||||
else:
|
||||
state = 'REGISTERED'
|
||||
expires = domainInfo['info']['stats']['daysUntilExpire']
|
||||
next = f"Expires in ~{expires} days"
|
||||
|
||||
own_domains = account_module.getDomains(account)
|
||||
own_domains = [x['name'] for x in own_domains]
|
||||
own_domains = [x.lower() for x in own_domains]
|
||||
if search_term in own_domains:
|
||||
next_action = f'<a href="/manage/{domain}">Manage</a>'
|
||||
elif state == 'OPENING':
|
||||
next = "Bidding opens in ~" + str(domainInfo['info']['stats']['blocksUntilBidding']) + " blocks"
|
||||
elif state == 'BIDDING':
|
||||
#! Check if the user has scanned the auction
|
||||
|
||||
next = "Reveal in ~" + str(domainInfo['info']['stats']['blocksUntilReveal']) + " blocks"
|
||||
elif state == 'REVEAL':
|
||||
next = "Reveal ends in ~" + str(domainInfo['info']['stats']['blocksUntilClose']) + " blocks"
|
||||
next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>'
|
||||
|
||||
message = ''
|
||||
if 'message' in request.args:
|
||||
message = request.args.get("message")
|
||||
|
||||
|
||||
return render_template("auction.html", account=account, sync=account_module.getNodeSync(),
|
||||
search_term=search_term,domain=domainInfo['info']['name'],
|
||||
raw=domainInfo,state=state, next=next,
|
||||
next_action=next_action, bids=bids,error=message)
|
||||
|
||||
|
||||
@app.route('/auction/<domain>/scan')
|
||||
def rescan_auction(domain):
|
||||
# Check if the user is logged in
|
||||
if request.cookies.get("account") is None:
|
||||
return redirect("/login")
|
||||
|
||||
account = account_module.check_account(request.cookies.get("account"))
|
||||
if not account:
|
||||
return redirect("/logout")
|
||||
|
||||
domain = domain.lower()
|
||||
|
||||
response = account_module.rescan_auction(account,domain)
|
||||
print(response)
|
||||
return redirect("/auction/" + domain)
|
||||
|
||||
@app.route('/auction/<domain>/bid')
|
||||
def bid(domain):
|
||||
# Check if the user is logged in
|
||||
if request.cookies.get("account") is None:
|
||||
return redirect("/login")
|
||||
|
||||
|
||||
if not account_module.check_account(request.cookies.get("account")):
|
||||
return redirect("/logout")
|
||||
|
||||
domain = domain.lower()
|
||||
|
||||
# Show confirm page
|
||||
total = float(request.args.get('bid')) + float(request.args.get('blind'))
|
||||
|
||||
action = f"Bid on {domain}/"
|
||||
content = f"Are you sure you want to bid on {domain}/?"
|
||||
content = "You are about to bid with the following details:<br><br>"
|
||||
content += f"Bid: {request.args.get('bid')} HNS<br>"
|
||||
content += f"Blind: {request.args.get('blind')} HNS<br>"
|
||||
content += f"Total: {total} HNS (excluding fees)<br><br>"
|
||||
|
||||
cancel = f"/auction/{domain}"
|
||||
confirm = f"/auction/{domain}/bid/confirm?bid={request.args.get('bid')}&blind={request.args.get('blind')}"
|
||||
|
||||
|
||||
|
||||
return render_template("confirm.html", account=account_module.check_account(request.cookies.get("account")),
|
||||
sync=account_module.getNodeSync(),action=action,
|
||||
domain=domain,content=content,cancel=cancel,confirm=confirm)
|
||||
|
||||
@app.route('/auction/<domain>/bid/confirm')
|
||||
def bid_confirm(domain):
|
||||
# Check if the user is logged in
|
||||
if request.cookies.get("account") is None:
|
||||
return redirect("/login")
|
||||
|
||||
|
||||
if not account_module.check_account(request.cookies.get("account")):
|
||||
return redirect("/logout")
|
||||
|
||||
domain = domain.lower()
|
||||
|
||||
# Send the bid
|
||||
response = account_module.bid(request.cookies.get("account"),domain,
|
||||
float(request.args.get('bid')),
|
||||
float(request.args.get('blind')))
|
||||
print(response)
|
||||
if 'error' in response:
|
||||
return redirect("/auction/" + domain + "?message=" + response['error'])
|
||||
|
||||
return redirect("/success?tx=" + response['hash'])
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
20
render.py
20
render.py
@ -134,3 +134,23 @@ def timestamp_to_readable_time(timestamp):
|
||||
dt_object = datetime.datetime.fromtimestamp(timestamp)
|
||||
readable_time = dt_object.strftime("%H:%M:%S %d %b %Y")
|
||||
return readable_time
|
||||
|
||||
def bids(data):
|
||||
html = ''
|
||||
for bid in data:
|
||||
lockup = bid['lockup']
|
||||
lockup = lockup / 1000000
|
||||
lockup = round(lockup, 2)
|
||||
html += "<tr>"
|
||||
html += f"<td>{lockup} HNS</td>"
|
||||
# TODO If revealed
|
||||
html += f"<td>Hidden until reveal</td>"
|
||||
html += f"<td>Hidden until reveal</td>"
|
||||
if bid['own']:
|
||||
html += "<td>You</td>"
|
||||
else:
|
||||
html += "<td>Unknown</td>"
|
||||
|
||||
html += "</tr>"
|
||||
|
||||
return html
|
115
templates/auction.html
Normal file
115
templates/auction.html
Normal file
@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Auction - FireWallet</title>
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/material-icons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
</head>
|
||||
|
||||
<body id="page-top">
|
||||
<div id="wrapper">
|
||||
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
|
||||
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="/">
|
||||
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
|
||||
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
|
||||
</a>
|
||||
<hr class="sidebar-divider my-0">
|
||||
<ul class="navbar-nav text-light" id="accordionSidebar">
|
||||
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/send"><i class="material-icons">send</i><span>Send HNS</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="receive"><i class="material-icons">call_received</i><span>Receive</span></a></li>
|
||||
</ul>
|
||||
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="d-flex flex-column" id="content-wrapper">
|
||||
<div id="content">
|
||||
<nav class="navbar navbar-expand bg-white shadow mb-4 topbar static-top navbar-light">
|
||||
<div class="container-fluid"><button class="btn btn-link d-md-none rounded-circle me-3" id="sidebarToggleTop" type="button"><i class="fas fa-bars"></i></button>
|
||||
<form class="d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 navbar-search" action="/search" method="get">
|
||||
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for domain" name="q" value="{{search_term}}"><button class="btn btn-primary py-0" type="submit"><i class="fas fa-search"></i></button></div>
|
||||
</form><span>Sync: {{sync}}%</span>
|
||||
<ul class="navbar-nav flex-nowrap ms-auto">
|
||||
<li class="nav-item dropdown d-sm-none no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><i class="fas fa-search"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown">
|
||||
<form class="me-auto navbar-search w-100">
|
||||
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for ...">
|
||||
<div class="input-group-append"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<div class="nav-item dropdown no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><span class="d-none d-lg-inline me-2 text-gray-600 small">{{account}}</span><img class="border rounded-circle img-profile" src="/assets/img/HNS.png"></a>
|
||||
<div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt fa-sm fa-fw me-2 text-gray-400"></i> Logout</a></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<h4 class="text-center" style="color: rgb(255,0,0);">{{error}}</h4>
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="stick-right">{{next_action|safe}}</div>
|
||||
<h4 class="card-title">{{domain}}/</h4>
|
||||
<h6 class="text-muted card-subtitle mb-2">{{next}}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid" style="margin-top: 50px;">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Bid</h4>
|
||||
<form action="/auction/{{search_term}}/bid"><label class="form-label">Bid</label><input class="form-control" type="number" placeholder="0.00" min="0" name="bid"><label class="form-label" style="margin-top: 10px;">Optional blind</label><input class="form-control" type="number" name="blind" placeholder="0.00" min="0">
|
||||
<div class="text-end" style="margin-top: 10px;"><input class="btn btn-primary" type="submit" value="Bid"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid" style="margin-top: 50px;">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Bids</h4><div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bid+Blind</th>
|
||||
<th>Bid</th>
|
||||
<th>Blind</th>
|
||||
<th>Owner</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{bids | safe}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="bg-white sticky-footer">
|
||||
<div class="container my-auto">
|
||||
<div class="text-center my-auto copyright"><span>Copyright © FireWallet 2023</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
</div><a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a>
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
84
templates/confirm.html
Normal file
84
templates/confirm.html
Normal file
@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="light" lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Confirm - FireWallet</title>
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&display=swap">
|
||||
<link rel="stylesheet" href="/assets/fonts/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/assets/fonts/material-icons.min.css">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
</head>
|
||||
|
||||
<body id="page-top">
|
||||
<div id="wrapper">
|
||||
<nav class="navbar align-items-start sidebar sidebar-dark accordion bg-gradient-primary p-0 navbar-dark">
|
||||
<div class="container-fluid d-flex flex-column p-0"><a class="navbar-brand d-flex justify-content-center align-items-center sidebar-brand m-0" href="/">
|
||||
<div class="sidebar-brand-icon"><img src="/assets/img/favicon.png" width="44"></div>
|
||||
<div class="sidebar-brand-text mx-3"><span>FireWallet</span></div>
|
||||
</a>
|
||||
<hr class="sidebar-divider my-0">
|
||||
<ul class="navbar-nav text-light" id="accordionSidebar">
|
||||
<li class="nav-item"><a class="nav-link" href="/"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/tx"><i class="fas fa-table"></i><span>Transactions</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/send"><i class="material-icons">send</i><span>Send HNS</span></a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="receive"><i class="material-icons">call_received</i><span>Receive</span></a></li>
|
||||
</ul>
|
||||
<div class="text-center d-none d-md-inline"><button class="btn rounded-circle border-0" id="sidebarToggle" type="button"></button></div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="d-flex flex-column" id="content-wrapper">
|
||||
<div id="content">
|
||||
<nav class="navbar navbar-expand bg-white shadow mb-4 topbar static-top navbar-light">
|
||||
<div class="container-fluid"><button class="btn btn-link d-md-none rounded-circle me-3" id="sidebarToggleTop" type="button"><i class="fas fa-bars"></i></button>
|
||||
<form class="d-none d-sm-inline-block me-auto ms-md-3 my-2 my-md-0 mw-100 navbar-search" action="/search" method="get">
|
||||
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for domain" name="q" value="{{search_term}}"><button class="btn btn-primary py-0" type="submit"><i class="fas fa-search"></i></button></div>
|
||||
</form><span>Sync: {{sync}}%</span>
|
||||
<ul class="navbar-nav flex-nowrap ms-auto">
|
||||
<li class="nav-item dropdown d-sm-none no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><i class="fas fa-search"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-end p-3 animated--grow-in" aria-labelledby="searchDropdown">
|
||||
<form class="me-auto navbar-search w-100">
|
||||
<div class="input-group"><input class="bg-light form-control border-0 small" type="text" placeholder="Search for ...">
|
||||
<div class="input-group-append"><button class="btn btn-primary py-0" type="button"><i class="fas fa-search"></i></button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<div class="nav-item dropdown no-arrow"><a class="dropdown-toggle nav-link" aria-expanded="false" data-bs-toggle="dropdown" href="#"><span class="d-none d-lg-inline me-2 text-gray-600 small">{{account}}</span><img class="border rounded-circle img-profile" src="/assets/img/HNS.png"></a>
|
||||
<div class="dropdown-menu shadow dropdown-menu-end animated--grow-in"><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt fa-sm fa-fw me-2 text-gray-400"></i> Logout</a></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<h3 class="text-dark mb-1">Are you sure you want to do this?</h3>
|
||||
<div class="card" style="margin-top: 50px;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{action}}</h4>
|
||||
<h6 class="text-muted card-subtitle mb-2">{{subtitle}}</h6>
|
||||
<p class="card-text">{{content|safe}}</p><a class="card-link" href="{{cancel}}">Cancel</a><a class="card-link" href="{{confirm}}">Confirm</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="bg-white sticky-footer">
|
||||
<div class="container my-auto">
|
||||
<div class="text-center my-auto copyright"><span>Copyright © FireWallet 2023</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
</div><a class="border rounded d-inline scroll-to-top" href="#page-top"><i class="fas fa-angle-up"></i></a>
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user