feat: Add initial domain management
All checks were successful
Build Docker / Build Docker (push) Successful in 25s

This commit is contained in:
Nathan Woodburn 2023-12-11 18:00:58 +11:00
parent 8a9f35e044
commit 64547ef6d4
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
9 changed files with 670 additions and 97 deletions

View File

@ -196,6 +196,40 @@ def updateNotifications(token, notifications):
return True
def updateDomainNotifications(token, domain, notifications):
# Get userID from userToken
if (token.count('$') != 1):
return False
token = token.split('$')
userID = token[0]
token = token[1]
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,))
user = cursor.fetchall()
cursor.close()
conn.close()
print(domain)
print(notifications)
# Read json token from first user
user = user[0][0]
user = json.loads(user)
for userDomain in user:
if (userDomain['name'] == domain):
userDomain['notifications'] = notifications
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(user), userID,))
conn.commit()
cursor.close()
conn.close()
return True
def updateNotificationProvider(token, provider,account):
# Get userID from userToken
if (token.count('$') != 1):

253
domains.py Normal file
View File

@ -0,0 +1,253 @@
import mysql.connector
from mysql.connector import Error
import dotenv
import os
import json
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import requests
import re
dotenv.load_dotenv()
db_config = {
'host': os.getenv('DB_HOST'),
'database': os.getenv('DB_NAME'),
'user': os.getenv('DB_USER'),
'password': os.getenv('DB_PASSWORD')
}
def addDomain(userID:int, domain:str):
# Get user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,))
user = cursor.fetchall()
cursor.close()
conn.close()
# Read json domains from first user
user = user[0][0]
userDomains = json.loads(user)
# Check if domain is already in use
for userDomain in userDomains:
if (userDomain['name'] == domain):
return False
# Add domain to user
userDomains.append({
"name": domain,
"status": "pending"
})
# Update user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID))
conn.commit()
cursor.close()
conn.close()
return True
def verifyDomain(domain:str):
# Remove any trailing slashes
domain = domain.rstrip('/')
# Remove any protocol
domain = domain.replace('https://', '')
domain = domain.replace('http://', '')
domain = domain.lower()
# Verify domain contains only valid characters
if (re.match("^[a-z0-9.-]*$", domain) == None):
return False
return domain
def deleteDomain(userID:int, domain:str):
# Get user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,))
user = cursor.fetchall()
cursor.close()
conn.close()
# Read json domains from first user
user = user[0][0]
userDomains = json.loads(user)
# Check if domain is already in use
for userDomain in userDomains:
if (userDomain['name'] == domain):
userDomains.remove(userDomain)
break
# Update user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID))
conn.commit()
cursor.close()
conn.close()
return True
def syncDomains():
# Verify connection to HSD node
try:
r = requests.get('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP')
+ ':' + os.getenv('HSD_PORT'))
if (r.status_code != 200):
return "HSD node is not responding"
# Check to make sure the node is synced
data = r.json()
if (data['chain']['progress'] < 0.999):
return "HSD node is not synced"
except:
return False
# Get all users
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("SELECT id, domains FROM users")
users = cursor.fetchall()
cursor.close()
conn.close()
# Loop through users
for user in users:
userID = user[0]
userDomains = json.loads(user[1])
# Loop through user domains
for userDomain in userDomains:
# Check if domain is pending
if (userDomain['status'] == 'pending'):
try:
print('Importing domain: ' + userDomain['name'])
r = requests.post('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP')
+ ':' + os.getenv('HSD_WALLET_PORT'), json={'method': 'importname', 'params': [userDomain['name']]})
if (r.status_code == 200):
# Update domain status
userDomain['status'] = 'added'
# Set some defaults
userDomain['transfering'] = 0
userDomain['next'] = 'none'
userDomain['when'] = 0
# Update user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID))
conn.commit()
cursor.close()
conn.close()
except:
pass
# Get domain info
r = requests.post('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP')
+ ':' + os.getenv('HSD_PORT'), json={'method': 'getnameinfo', 'params': [userDomain['name']]})
print(json.dumps(r.json(), indent=4))
if (r.status_code == 200):
data = r.json()
# Check if domain is registered
info = data['result']['info']
if (userDomain['transfering'] != info['transfer']):
# Update domain status
alert('transfer', userDomain['name'], userID)
userDomain['transfering'] = info['transfer']
if 'stats' in info:
if 'blocksUntilExpire' in info['stats']:
# Update domain status
userDomain['next'] = 'expires'
previous = userDomain['when']
userDomain['when'] = info['stats']['blocksUntilExpire']
if (crossTimeAlert(userDomain['next'], previous, userDomain['when'])):
alert('expire', userDomain['name'], userID)
elif 'blocksUntilBidding' in info['stats']:
# Update domain status
userDomain['next'] = 'opens for bidding'
userDomain['when'] = info['stats']['blocksUntilBidding']
# Update user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID))
conn.commit()
cursor.close()
conn.close()
else:
return "Failed to get info about domain: " + userDomain['name'] + "<br>" + str(r.text)
return "Finished syncing domains"
def alert(event,domain,user):
# TODO this later
pass
def crossTimeAlert(event,was, now):
# Check for each of these times to see if we should alert
month = 4320
week = 1008
# If the time crossed the month mark
if (was > month and now <= month):
return True
# If the time crossed the week mark
if (was > week and now <= week):
return True
return False
def getCachedDomainInfo(domain):
# Get domain info from user domains
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()
cursor.execute("SELECT domains FROM users")
users = cursor.fetchall()
cursor.close()
conn.close()
# Loop through users
for user in users:
userDomains = json.loads(user[0])
# Loop through user domains
for userDomain in userDomains:
if (userDomain['name'] == domain):
return userDomain
return False
def blocksToTime(blocks):
# Get minutes
minutes = blocks * 10
years, minutes = divmod(minutes, 525600)
days, minutes = divmod(minutes, 1440)
hours, minutes = divmod(minutes, 60)
# Build the string
time_string = ""
if years:
time_string += f"{years} {'year' if years == 1 else 'years'}, "
if days:
time_string += f"{days} {'day' if days == 1 else 'days'}, "
if hours and not years:
time_string += f"{hours} {'hour' if hours == 1 else 'hours'}, "
if minutes and not years and not days:
time_string += f"{minutes} {'min' if minutes == 1 else 'mins'}"
return time_string.rstrip(', ')

182
main.py
View File

@ -9,6 +9,7 @@ import time
import json
import account
import threading
import domains
dotenv.load_dotenv()
@ -107,7 +108,20 @@ def dashboard():
if (account.verifyUser(request.cookies.get('user_token'))):
# Get user data
user = account.getUser(request.cookies.get('user_token'))
domains = user['domains']
error = ""
if (request.args.get('error')):
error = request.args.get('error')
success = ""
if (request.args.get('success')):
success = request.args.get('success')
# For each domain in domains get only the name
domains = []
for domain in user['domains']:
domains.append(domain['name'])
notifications = user['notifications']
email = user['email'] + ' <a href="/test?service=email">Test</a>'
@ -140,7 +154,8 @@ def dashboard():
return render_template('dashboard.html', domains=domains, notifications=notifications,
email=email, discord=discord, telegram=telegram,
expiry_week=expiry_week,expiry_month=expiry_month)
expiry_week=expiry_week,expiry_month=expiry_month,
error=error,success=success,admin=user['admin'])
return redirect('/login')
@ -200,30 +215,161 @@ def notification_options():
if (account.verifyUser(request.cookies.get('user_token'))):
# Get user data
user = account.getUser(request.cookies.get('user_token'))
notifications = user['notifications']
expiry_week = {
"email": False,
"discord": False,
"telegram": False
"email": False,
"discord": False,
"telegram": False
}
expiry_month = {
"email": False,
"discord": False,
"telegram": False
}
for key in request.form:
if (key.endswith('_week')):
key = key[:-5]
expiry_week[key] = True
elif (key.endswith('_month')):
key = key[:-6]
expiry_month[key] = True
notifications['expiry_week'] = expiry_week
notifications['expiry_month'] = expiry_month
account.updateNotifications(request.cookies.get('user_token'), notifications)
return redirect('/dashboard')
if not request.form.get('domain'):
notifications = user['notifications']
for key in request.form:
if (key.endswith('_week')):
key = key[:-5]
expiry_week[key] = True
elif (key.endswith('_month')):
key = key[:-6]
expiry_month[key] = True
notifications['expiry_week'] = expiry_week
notifications['expiry_month'] = expiry_month
account.updateNotifications(request.cookies.get('user_token'), notifications)
return redirect('/dashboard')
else:
notifications = {
"expiry_week": {
"email": False,
"discord": False,
"telegram": False
},
"expiry_month": {
"email": False,
"discord": False,
"telegram": False
}
}
domainList = user['domains']
domain = request.form.get('domain').lower()
for key in request.form:
if (key.endswith('_week')):
key = key[:-5]
notifications['expiry_week'][key] = True
elif (key.endswith('_month')):
key = key[:-6]
notifications['expiry_month'][key] = True
account.updateDomainNotifications(request.cookies.get('user_token'),
domain,notifications)
return redirect('/' + domain + '/info')
#region domains
@app.route('/new-domain', methods=['POST'])
def new_domain():
# Check if user is logged in
if 'user_token' in request.cookies:
if (account.verifyUser(request.cookies.get('user_token'))):
# Get user data
user = account.getUser(request.cookies.get('user_token'))
domain = request.form.get('domain')
# Verify domain
domain = domains.verifyDomain(domain)
if (domain):
# Add domain to user
if (domains.addDomain(user['id'], domain)):
return redirect('/dashboard?success=Domain added')
else:
return redirect('/dashboard?error=Unable to add domain')
else:
return redirect('/dashboard?error=Invalid domain')
return redirect('/login')
@app.route('/<domain>/delete')
def delete_domain(domain):
# Check if user is logged in
if 'user_token' in request.cookies:
if (account.verifyUser(request.cookies.get('user_token'))):
# Get user data
user = account.getUser(request.cookies.get('user_token'))
# Delete domain from user
if (domains.deleteDomain(user['id'], domain)):
return redirect('/dashboard?success=Domain deleted')
else:
return redirect('/dashboard?error=Unable to delete domain')
return redirect('/login')
@app.route('/sync')
def sync_domains():
# Check if user is logged in
if 'user_token' in request.cookies:
if (account.verifyUser(request.cookies.get('user_token'))):
# Get user data
user = account.getUser(request.cookies.get('user_token'))
if (user['admin'] == False):
return redirect('/dashboard?error=You are not an admin')
# Sync domains
result =domains.syncDomains()
return redirect('/dashboard?error=' + result)
return redirect('/login')
@app.route('/<domain>/info')
def domain(domain):
# Check if user is logged in
if 'user_token' in request.cookies:
if (account.verifyUser(request.cookies.get('user_token'))):
# Get domain info
domainInfo = domains.getCachedDomainInfo(domain)
print(domainInfo)
if (domainInfo):
if (domainInfo['status'] == 'pending'):
return redirect('/dashboard?error=Domain is pending<br>Please wait a few minutes')
next = domainInfo['next']
when_blocks = domainInfo['when']
when_time = domains.blocksToTime(when_blocks)
transfering = domainInfo['transfering'] == 1
expiry_week = {
"email": False,
"discord": False,
"telegram": False
}
expiry_month = {
"email": False,
"discord": False,
"telegram": False
}
if ('notifications' in domainInfo):
if ('expiry_week' in domainInfo['notifications']):
expiry_week = domainInfo['notifications']['expiry_week']
if ('expiry_month' in domainInfo['notifications']):
expiry_month = domainInfo['notifications']['expiry_month']
return render_template('info.html', domain=str(domain).capitalize(),
next=next,when_blocks=when_blocks,when_time=when_time,
transfering=transfering,expiry_week=expiry_week,
expiry_month=expiry_month)
else:
return render_template('info.html', domain=str(domain).capitalize())
return redirect('/login')
#endregion
@app.route('/assets/<path:path>')
def send_assets(path):

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Products - HNS Alert</title>
<title>Dashboard - HNS Alert</title>
<meta name="twitter:image" content="https://alert.woodburn.au/assets/img/favicon.png">
<meta property="og:description" content="Alert system for Handshake domains">
<meta property="og:type" content="website">
@ -12,25 +12,30 @@
<meta name="description" content="Alert system for domains on the Handshake chain">
<meta property="og:title" content="HNSAlert">
<meta name="twitter:card" content="summary">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="logout">Logout</a></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a>{%if admin%}
<a href="/sync">Sync</a>
{%endif%}<a class="btn btn-primary shadow" role="button" href="logout">Logout</a></div>
</nav>
<section class="py-5">
<div class="container py-5">
<h1 class="text-center" style="color: rgb(255,0,0);">{{error | safe}}</h1>
<h1 class="text-center" style="color: rgb(0,255,0);">{{success | safe}}</h1>
<div class="row mb-4 mb-lg-5">
<div class="col-md-8 col-xl-6 text-center mx-auto">
<h2 class="fw-bold">Dashboard</h2>
@ -77,6 +82,29 @@
</div>
</div>
</div>
<div class="row mx-auto">
<div class="col">
<div class="card">
<div class="card-body">
<h4 class="card-title">Domains</h4><ul class="list-group">
<li class="list-group-item">
<form action="/new-domain" method="post"><input class="form-control" type="text" name="domain" placeholder="newdomain/" />
</form>
</li>
{% for domain in domains %}
<li class="list-group-item">
<span style="margin-right: 20px;">{{domain}}/</span> <div class="btn-group" role="group" style="position: absolute;height: 75%;right: 10px;top: 0.3em;">
<a class="btn btn-primary" role="button" style="padding: 0px;padding-left: 36px;padding-right: 36px;" href="/{{domain}}/info">Info</a>
<a class="btn btn-primary" role="button" style="padding: 0px;padding-right: 32px;padding-left: 32px;" href="/{{domain}}/delete">Delete</a>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="bg-dark">
@ -99,7 +127,7 @@
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
@ -109,8 +137,8 @@
</div>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bold-and-dark.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>

View File

@ -20,22 +20,22 @@
"url": "https://alert.woodburn.au"
}
</script>
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
</nav>
<header class="bg-dark">
<div class="container pt-4 pt-xl-5">
@ -48,9 +48,9 @@
</div>
<div class="col-12 col-lg-10 mx-auto">
<div class="position-relative" style="display: flex;flex-wrap: wrap;justify-content: flex-end;">
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-15%, 35%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.8" src="assets/img/products/3.jpg"></div>
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-5%, 20%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.4" src="assets/img/products/2.jpg"></div>
<div style="position: relative;flex: 0 0 60%;transform: translate3d(0, 0%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.25" src="assets/img/products/1.jpg"></div>
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-15%, 35%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.8" src="/assets/img/products/3.jpg"></div>
<div style="position: relative;flex: 0 0 45%;transform: translate3d(-5%, 20%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.4" src="/assets/img/products/2.jpg"></div>
<div style="position: relative;flex: 0 0 60%;transform: translate3d(0, 0%, 0);"><img class="img-fluid" data-bss-parallax="" data-bss-parallax-speed="0.25" src="/assets/img/products/1.jpg"></div>
</div>
</div>
</div>
@ -164,7 +164,7 @@
<div class="col mb-4">
<div class="d-flex flex-column align-items-center align-items-sm-start">
<p class="bg-dark border rounded border-dark p-4">Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.</p>
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="assets/img/team/avatar2.jpg">
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="/assets/img/team/avatar2.jpg">
<div>
<p class="fw-bold text-primary mb-0">John Smith</p>
<p class="text-muted mb-0">Erat netus</p>
@ -175,7 +175,7 @@
<div class="col mb-4">
<div class="d-flex flex-column align-items-center align-items-sm-start">
<p class="bg-dark border rounded border-dark p-4">Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.</p>
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="assets/img/team/avatar4.jpg">
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="/assets/img/team/avatar4.jpg">
<div>
<p class="fw-bold text-primary mb-0">John Smith</p>
<p class="text-muted mb-0">Erat netus</p>
@ -186,7 +186,7 @@
<div class="col mb-4">
<div class="d-flex flex-column align-items-center align-items-sm-start">
<p class="bg-dark border rounded border-dark p-4">Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.</p>
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="assets/img/team/avatar5.jpg">
<div class="d-flex"><img class="rounded-circle flex-shrink-0 me-3 fit-cover" width="50" height="50" src="/assets/img/team/avatar5.jpg">
<div>
<p class="fw-bold text-primary mb-0">John Smith</p>
<p class="text-muted mb-0">Erat netus</p>
@ -251,7 +251,7 @@
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
@ -261,8 +261,8 @@
</div>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bold-and-dark.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>

112
templates/info.html Normal file
View File

@ -0,0 +1,112 @@
<!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>{{domain}}/ - HNS Alert</title>
<meta name="twitter:image" content="https://alert.woodburn.au/assets/img/favicon.png">
<meta property="og:description" content="Alert system for Handshake domains">
<meta property="og:type" content="website">
<meta property="og:image" content="https://alert.woodburn.au/assets/img/favicon.png">
<meta name="description" content="Alert system for domains on the Handshake chain">
<meta property="og:title" content="HNSAlert">
<meta name="twitter:card" content="summary">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="/login">Account</a></div>
</nav>
<section class="py-5">
<h1 class="text-center">{{domain}}/ Info</h1>
<div class="container py-5">
<div class="row mx-auto">
<div class="col">
<div class="card">
<div class="card-body">
<section class="position-relative py-4 py-xl-5">
<div class="container">
<div class="row mb-5">
<div class="col-md-8 col-xl-6 text-center mx-auto">
<h2>{{domain}}/ Status</h2>
<p class="w-lg-50">{{domain}} {{next}} in {{when_blocks}} blocks (~{{when_time}})<br>{%if transfering %}<br>{{domain}} is currently transferring<br>{%endif%}</p>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h4 class="card-title">Notification options</h4>
<h6 class="text-muted card-subtitle mb-2">These are per domain options. These options will override global settings once you save them</h6>
<form method="post" action="/notification-options"><input class="form-control" type="hidden" name="domain" value="{{domain}}">
<ul class="list-group">
<li class="list-group-item"><span style="display: block;">Domain expiry 1 week notice</span>{% for platform, enabled in expiry_week.items() %}
<div class="form-check form-switch form-check-inline">
<input id="formCheck-{{ loop.index }}" class="form-check-input" type="checkbox" name="{{ platform }}_week" {% if enabled %}checked{% endif %}/>
<label class="form-check-label" for="formCheck-{{ loop.index }}">{{ platform|capitalize }}</label>
</div>
{% endfor %}</li>
<li class="list-group-item"><span style="display: block;">Domain expiry 1 month notice</span>{% for platform, enabled in expiry_month.items() %}
<div class="form-check form-switch form-check-inline">
<input id="formCheck-{{ loop.index }}" class="form-check-input" type="checkbox" name="{{ platform }}_month" {% if enabled %}checked{% endif %}/>
<label class="form-check-label" for="formCheck-{{ loop.index }}">{{ platform|capitalize }}</label>
</div>
{% endfor %}</li>
</ul>
<div style="text-align: right;"><input class="btn btn-primary" type="submit" value="Save" style="margin-top: 20px;"></div>
</form>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="bg-dark">
<div class="container py-4 py-lg-5">
<div class="row justify-content-center">
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">Services</h3>
<ul class="list-unstyled">
<li><a href="https://hnshosting.au" target="_blank">Wordpress Hosting</a></li>
<li><a href="https://faucet.woodburn.au" target="_blank">Domain Faucet</a></li>
<li><a href="https://github.com/nathanwoodburn" style="margin-top: 0px;" target="_blank">Other Projects</a></li>
</ul>
</div>
<div class="col-sm-4 col-md-3 text-center text-lg-start d-flex flex-column">
<h3 class="fs-6 fw-bold">About</h3>
<ul class="list-unstyled">
<li><a href="https://github.com/nathanwoodburn" target="_blank">Github</a></li>
<li><a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a></li>
<li><a href="https://l.woodburn.au/donate" target="_blank">Donate</a></li>
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
<hr>
<div class="text-muted d-flex justify-content-between align-items-center pt-3">
<p class="mb-0">Copyright © 2023 HNS Alert</p>
</div>
</div>
</footer>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>

View File

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Products - HNS Alert</title>
<title>Link Service - HNS Alert</title>
<meta name="twitter:image" content="https://alert.woodburn.au/assets/img/favicon.png">
<meta property="og:description" content="Alert system for Handshake domains">
<meta property="og:type" content="website">
@ -12,22 +12,22 @@
<meta name="description" content="Alert system for domains on the Handshake chain">
<meta property="og:title" content="HNSAlert">
<meta name="twitter:card" content="summary">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="logout">Logout</a></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="logout">Logout</a></div>
</nav>
<section class="py-5">
<div class="container py-5">
@ -83,7 +83,7 @@
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
@ -93,8 +93,8 @@
</div>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bold-and-dark.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>

View File

@ -12,22 +12,22 @@
<meta name="description" content="Alert system for domains on the Handshake chain">
<meta property="og:title" content="HNSAlert">
<meta name="twitter:card" content="summary">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
</nav>
<section class="py-5">
<div class="container py-5">
@ -77,7 +77,7 @@
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
@ -87,8 +87,8 @@
</div>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bold-and-dark.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>

View File

@ -12,22 +12,22 @@
<meta name="description" content="Alert system for domains on the Handshake chain">
<meta property="og:title" content="HNSAlert">
<meta name="twitter:card" content="summary">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800&amp;display=swap">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="/assets/css/Login-Form-Basic-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-md sticky-top py-3 navbar-dark" id="mainNav">
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
<div class="container"><a class="navbar-brand d-flex align-items-center" href="/"><img src="/assets/img/server-icon.png" style="margin-right: 20px;"><span>HNS Alert</span></a><a class="btn btn-primary shadow" role="button" href="login">Account</a></div>
</nav>
<section class="py-5">
<div class="container py-5">
@ -49,7 +49,7 @@
<div class="mb-3"><input class="form-control" type="email" name="email" placeholder="Email"></div>
<div class="mb-3"><input class="form-control" type="password" name="password" placeholder="Password"></div>
<div class="mb-3"><button class="btn btn-primary shadow d-block w-100" type="submit">Sign up</button></div>
<p class="text-muted">Already have an account?&nbsp;<a href="login.html">Log in</a></p>
<p class="text-muted">Already have an account?&nbsp;<a href="/login.html">Log in</a></p>
</form>
</div>
</div>
@ -77,7 +77,7 @@
</ul>
</div>
<div class="col-lg-3 text-center text-lg-start d-flex flex-column align-items-center order-first align-items-lg-start order-lg-last">
<div class="fw-bold d-flex align-items-center mb-2"><img src="assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<div class="fw-bold d-flex align-items-center mb-2"><img src="/assets/img/favicon-32x32.png" style="margin-right: 10px;"><span>HNS Alert</span></div>
<p class="text-muted">A Woodburn/ project</p>
</div>
</div>
@ -87,8 +87,8 @@
</div>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bold-and-dark.js"></script>
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/bold-and-dark.js"></script>
</body>
</html>