Push a bunch of uncommitted UI changes

This commit is contained in:
Jonathan Carter
2025-07-17 09:53:02 +02:00
parent 007a959274
commit b61c45b5f4
15 changed files with 252 additions and 62 deletions

View File

@@ -10,10 +10,13 @@
<div class="radios">
<label class="radio">
<input type="radio" name="rsvp" />
Preconfigured partitioning &nbsp;
<label class="radio">
<input type="radio" name="rsvp" disabled />
Erase entire disk
</label>
<label class="radio">
<input type="radio" name="rsvp" />
<input type="radio" name="rsvp" disabled />
Install alongside another operating system
</label>
<label class="radio">
@@ -23,7 +26,7 @@
</div>
<div class="columns" width="100%">
<div class="column"><p> Current layout: </p></div>
<div class="column"><p> <br>Proposed layout: </p></div>
</div>
<!--
@@ -60,7 +63,26 @@
<br>
-->
{% for disk in blockdevs["blockdevices"] %}
<!-- {% for disk in blockdevs["blockdevices"] if disk["name"] != "zram0"%} -->
<!--
<h1> Raw data </h1>
{{ disk }}
<h1> Children </h1>
{% for child in disk['children'] %}
<h2> <big><b> Child </b></big> </h2>
Path: {{ child['path'] }}
fstype: {{ child['fstype'] }}
fssize: {{ child['fssize'] }}
{{ child }}
{% endfor %}
-->
<div style=""> <!-- partition bars -->
<div style="background-color: gray; padding: 4px 6px 7px 6px; border-radius: 10px;">
@@ -68,12 +90,12 @@
<small><small>{{ disk['name'] }} ({{(disk['size']/1024000000)|round(0)|int }} GB) - Serial: {{ disk['serial'] }}</small></small>
<div class="columns">
{% for child in disk['children'] %}
<a hx-get="/disks/partition/sda" hx-swap="outerHTML"/>
<a hx-get="/disks/partition/{{ child['name'] }}" hx-swap="outerHTML"/>
<div class="column is-narrow is-primary">
<div class="box" style="min-width: 220px; border-radius: 0 0 0px 5px; height: 60px; padding-top: 5px;">
<p class="is-tiny">{{ child['name'] }} ({{ (child['size']/1024000000)|round(2) }} GiB) </p>
<p class="title is-5"><span class="tag is-black">/boot/efi</span>
<span class="tag is-link">vfat</span>
<p class="title is-5"><span class="tag is-black">{{ child['mountpoint'] }}</span>
<span class="tag is-link">{{ child['fstype'] }}</span>
</p>
</div>
</div>

View File

@@ -3,7 +3,7 @@
<div class="modal-content">
<div style="background-color: #424242; padding: 15px; border-radius: 15px; boders: none; color: white;">
<h1 class="is-size-4"> Edit partition: /dev/sda2 </h1>
<h1 class="is-size-4"> Edit partition: {{ partition }} </h1>
<p class="title is-6"><span class="tag is-black">Primary Partition</span>
<span>

View File

@@ -18,9 +18,10 @@ def disks():
else:
previous_step_url = menu[menu.index("disks")-1]
add_disks_button = '&nbsp; <button class="button iis-info"> <img src="/static/icons/tab-new-symbolic.svg"> &nbsp; Add Disk or Filesystem </button>'
add_swap_button = '&nbsp; <button class="button iis-info"> <img src="/static/icons/tab-new-symbolic.svg"> &nbsp; Swap Configuration </button>'
add_disks_button = '&nbsp; <button class="button iis-info"> <i class="fa fa-hdd-o" aria-hidden="true"></i> &nbsp; Add Disk or Filesystem </button>'
add_swap_button = '&nbsp; <button class="button iis-info"> <i class="fa fa-object-group" aria-hidden="true"></i> &nbsp; Swap Configuration </button>'
bottom_menu = add_disks_button + add_swap_button
build_summary()
return render_template('disks.html', blockdevs=blockdevs,
menu=current_app.config['CONFIG']['settings']['menu'],
@@ -35,8 +36,11 @@ def disks_partition(part):
"""
Partition modal for the webui partition screen.
"""
print("Partition is: " + part)
partition = part
return render_template('disks_partition.html',
menu=current_app.config['CONFIG']['settings']['menu'])
menu=current_app.config['CONFIG']['settings']['menu'],
partition=partition)
def build_menu():
@@ -47,4 +51,15 @@ def build_menu():
#current_app.config['CONFIG']['settings']['menu']['welcome'] = (build_stringlist()['menu_item'], "/welcome", 10)
current_app.config['CONFIG']['settings']['menu']['disks'] = ("Disks", "/disks", 30)
def build_summary():
"""
Write up a summary of what this module will do.
"""
current_app.config['CONFIG']['Summary']['disks'] = {}
current_app.config['CONFIG']['Summary']['disks']['heading'] = "Disks and Partitions"
current_app.config['CONFIG']['Summary']['disks']['bleh'] = current_app.config['CONFIG']['recipe']['popcon']['enable_popcon']
current_app.config['CONFIG']['Summary']['disks']['text'] = "Pre-configured disk layout."
return("ok?")
build_menu()

View File

@@ -1,9 +1,15 @@
{% extends "layout.html" %}
{% block body %}
<p><b> Installing...</b></p>
<center>
<p style="position: absolute: left: 45px; top: 10px;"> <big><big> Installing...</big></big></p> <br />
<img style="width: 400px; padding: 20px;" src="/static/slide1.png" /> <br />
<p> Installing system, the rest of the process is automated. </p>
<p style="margin: 20px;"> <input style="background-color: #777777;" type="checkbox" id="reboot" name="reboot" value="reboot" checked>
<label for="reboot"> Reboot automatically when ready </label></p><br>
</center>
<p> Or are we... </p>
<div style="position: absolute; padding: 10px; background-color: #26495e; left: 40px; right: 40px; bottom: 40px; height: text-align: middle;"> <center> Initializing... </center> </div>
</div>
</div>

View File

@@ -15,17 +15,31 @@ def webui_software():
else:
previous_step_url = menu[menu.index("software")-1]
sources_button = '&nbsp; <button class="button iis-info"> <img src="/static/icons/tab-new-symbolic.svg"> &nbsp; Edit Sources </button>'
blends_button = '&nbsp; <button class="button iis-info"> <img src="/static/icons/tab-new-symbolic.svg"> &nbsp; Install a Blend </button>'
sources_button = '&nbsp; <button class="button iis-info"> <i class="fa fa-cloud-download" aria-hidden="true"></i> &nbsp; Edit Sources </button>'
blends_button = '&nbsp; <button class="button iis-info"> <i class="fa fa-download" aria-hidden="true"></i> &nbsp; Install a Blend </button>'
bottom_menu = sources_button + blends_button
build_summary()
return render_template('software.html',
menu=current_app.config['CONFIG']['settings']['menu'],
menu_order=current_app.config['CONFIG']['settings']['menu_order'].split(),
previous_step = previous_step_url,
next_step = next_step_url,
bottom_menu = bottom_menu)
bottom_menu = bottom_menu,
popcon=current_app.config['CONFIG']['recipe']['popcon']['enable_popcon'])
@bp.route('/software/settings', methods=['GET', 'POST'])
def software_settings():
"""
Receive settings for the software applet.
"""
if request.method == 'POST':
popcon = "popcon" in request.form
current_app.config['CONFIG']['recipe']['popcon']['enable_popcon'] = popcon
print(current_app.config['CONFIG']['recipe']['popcon']['enable_popcon'])
build_summary()
return ('', 204)
def build_menu():
@@ -36,5 +50,16 @@ def build_menu():
#current_app.config['CONFIG']['settings']['menu']['welcome'] = (build_stringlist()['menu_item'], "/welcome", 10)
current_app.config['CONFIG']['settings']['menu']['software'] = ("Software", "/software", 40)
build_menu()
def build_summary():
"""
Write up a summary of what this module will do.
"""
current_app.config['CONFIG']['Summary']['software'] = {}
current_app.config['CONFIG']['Summary']['software']['heading'] = "Software"
current_app.config['CONFIG']['Summary']['software']['bleh'] = current_app.config['CONFIG']['recipe']['popcon']['enable_popcon']
current_app.config['CONFIG']['Summary']['software']['text'] = "Participate in Popularity Contest: " + str(current_app.config['CONFIG']['recipe']['popcon']['enable_popcon']) + "<br/>No desktop environment selected."
return("ok?")
build_menu()

View File

@@ -5,11 +5,23 @@
<br>
<div style="background-color: gray; padding: 4px 6px 7px 6px; border-radius: 10px; margin-bottom: 20px;">
<p> <span style="width: 100%; border-radius: 8px 8px 0 0;" class="tag is-black">
Popularity Contest</span>
<div style="padding-left: 15px; padding-right: 15px; background-color: #fff; color: #000;">
<p>The Popularity Contest (popcon) is a programme where anonymous data is sent back to Debian, tracking the number of packages installed. More information can be obtained at https://popcon.debian.org. <br/><br/> <b>Would you like to participate in popcon?</b><p>
<div style="background-color: gray; padding: 4px 6px 7px 6px;
border-radius: 10px; margin-bottom: 20px;">
<span style="width: 100%; border-radius: 8px 8px 0 0;"
class="tag is-black">
Popularity Contest
</span>
<div style="padding-left: 15px; padding-right: 15px; background-color: #fff;
color: #000;">
<p>The Popularity Contest (popcon) is a programme where anonymous data is sent back to Debian, tracking the number of packages installed. More information can be obtained at https://popcon.debian.org.</p> <br>
<input style="background-color: #777777;" type="checkbox" id="popcon"
name="popcon" hx-post="/software/settings" hx-trigger="change"
{% if popcon %} checked {% endif %}>
<label for="popcon"> Participate in PopCon </label><br>
</div>
</div>

View File

@@ -14,13 +14,19 @@ def summary_index():
previous_step_url = menu[menu.index("summary")-1]
next_step_url = "install"
print("previous step url: ", previous_step_url)
config = current_app.config['CONFIG']
print (current_app.config['CONFIG']['Summary'])
html = (str(current_app.config['CONFIG']['Summary']['welcome']) + "<br>" + str(current_app.config['CONFIG']['Summary']['software']))
summary = config['Summary']
return render_template('summary.html',
menu=current_app.config['CONFIG']['settings']['menu'],
menu_order=current_app.config['CONFIG']['settings']['menu_order'].split(),
previous_step=previous_step_url,
next_step=next_step_url)
next_step=next_step_url,
config=config,
html=html,
summary=summary)
def build_menu():

View File

@@ -3,7 +3,25 @@
<p><b> Ready to install! Please review all changes.</b></p>
<p> This is the last chance to back out before committing to instal, please ensure all the details are correct. </p>
<p> This is the last chance to back out before committing to install, please ensure all the details are correct. </p>
<br>
{% for module in summary %}
<h3> <big> {{ summary[module]['heading'] }} </big> </h3>
<!-- {{ summary[module]['bleh'] }}
{{ summary[module]['settings'] }} -->
{{ summary[module]['text']|safe }}
<br /><br />
{% endfor %}
<br><br/>
<!-- <p>Config is: <code> {{ config }} </code></p> -->
</div>
</div>

View File

@@ -10,7 +10,8 @@ def set_language(LANG):
"""
Sets language for this applet
"""
translations = gettext.translation("users", './applets/users/locales', fallback=True, languages=[LANG])
translations = gettext.translation("users", './applets/users/locales',
fallback=True, languages=[LANG])
translations.install()
_ = translations.gettext
@@ -32,18 +33,49 @@ def users_root():
else:
previous_step_url = menu[menu.index("users")-1]
root_button = '&nbsp; <button class="button is-light"> <img width="16px;" src="/static/icons/emblem-default-symbolic.svg"> &nbsp; Setup root user </button>'
ldap_button = '&nbsp; <button class="button is-light"> <img width="16px;" src="/static/icons/emblem-default-symbolic.svg"> &nbsp; Connect LDAP </button>'
ad_button = '&nbsp; <button class="button is-light"> <img width="16px;" src="/static/icons/emblem-default-symbolic.svg"> &nbsp; Connect AD </button>'
csv_button = '&nbsp; <button class="button is-light"> <img width="16px;" src="/static/icons/emblem-default-symbolic.svg"> &nbsp; Import CSV </button>'
root_button = '&nbsp; <button class="button is-light"> <i class="fa fa-user" aria-hidden="true"></i> &nbsp; Setup root user </button>'
ldap_button = '&nbsp; <button class="button is-light"> <i class="fa fa-address-card" aria-hidden="true"></i> &nbsp; Connect LDAP </button>'
ad_button = '&nbsp; <button class="button is-light"> <i class="fa fa-address-card" aria-hidden="true"></i> &nbsp; Connect AD </button>'
csv_button = '&nbsp; <button class="button is-light"> <i class="fa fa-users" aria-hidden="true"></i> &nbsp; Import CSV </button>'
bottom_menu = root_button + ldap_button + ad_button + csv_button
initial_user = current_app.config['CONFIG']['recipe']['users']['users'][0]
build_summary()
return render_template('users.html', string_dict=string_dict,
menu=current_app.config['CONFIG']['settings']['menu'],
menu_order=current_app.config['CONFIG']['settings']['menu_order'].split(),
previous_step=previous_step_url,
next_step=next_step_url,
bottom_menu=bottom_menu)
bottom_menu=bottom_menu,
initial_user=initial_user)
@bp.route('/users/user-check/', methods=['GET', 'POST', 'PUT'])
def check_user():
if request.method == 'POST':
print("Ok, so method is POST")
print("keys are: ", request.form.keys())
username = request.form["username"]
fullname = request.form["fullname"]
password = request.form["password"]
password_confirm = request.form["password_confirm"]
print(current_app.config['CONFIG']['recipe']['users']['users'][0])
print("fullname is: " + fullname)
print("password is: " + password)
print("password_confirm is: " + password_confirm)
current_app.config['CONFIG']['recipe']['users']['users'][0]['username'] = username
current_app.config['CONFIG']['recipe']['users']['users'][0]['fullname'] = fullname
current_app.config['CONFIG']['recipe']['users']['users'][0]['password'] = password
current_app.config['CONFIG']['recipe']['users']['users'][0]['password_confirm'] = password_confirm
if username in ["root", "games"]:
return('<p class="has-text-danger"><b>That username is not available</b> </p>')
else:
return('<p class="has-text-success"><b>That username is available</b> </p>')
build_summary()
return("bleh")
def build_stringlist():
@@ -55,6 +87,7 @@ def build_stringlist():
string_dict['initial_user_text'] = _("Let's set up an initial user.")
string_dict['full_name'] = _("Full Name")
string_dict['user_name'] = _("Username")
string_dict['user_name_hint'] = _("One word, all lowercase")
string_dict['user_name_available'] = _("This username is available")
string_dict['user_name_not_available'] = _("This username is not available")
string_dict['user_name_reserved'] = _("This username is reserved by the system")
@@ -73,4 +106,16 @@ def build_menu():
current_app.config['CONFIG']['settings']['menu']['users'] = (build_stringlist()['menu_item'], "/users", 20)
print(current_app.config['CONFIG']['settings']['menu'])
def build_summary():
"""
Write up a summary of what this module will do.
"""
current_app.config['CONFIG']['Summary']['users'] = {}
current_app.config['CONFIG']['Summary']['users']['heading'] = "Users and Identity"
current_app.config['CONFIG']['Summary']['users']['bleh'] = current_app.config['CONFIG']['recipe']['popcon']['enable_popcon']
current_app.config['CONFIG']['Summary']['users']['settings'] = current_app.config['CONFIG']['recipe']['users']['users']
current_app.config['CONFIG']['Summary']['users']['text'] = "Add primary user with the username: " + str(current_app.config['CONFIG']['recipe']['users']['users'][0]['username'])
return("ok?")
build_menu()

View File

@@ -5,45 +5,57 @@
<br>
<form action="/users" method="POST">
<img src="/static/icons/avatar-default.svg" width="160px" style="float: left; position: absolute;" />
<div style="margin-left: 180px;">
<div class="field">
<label>{{ string_dict['full_name'] }}</label> <div class="control"><input class="input" type="text" placeholder="Text input">
<label>{{ string_dict['full_name'] }}</label>
<div class="control has-icons-left has-icons-right">
<input class="input" name="fullname" type="text" placeholder="Full Name" value="{% if initial_user['fullname'] %}{{ initial_user['fullname'] }}{% endif %}" hx-post="/users/user-check" hx-trigger="keyup changed delay:500ms" hx-target="#username-name">
<span class="icon is-small is-left">
<i class="fa fa-user-circle-o" aria-hidden="true"></i>
</span>
</div>
</div>
<div class="field">
<label>{{ string_dict['user_name'] }}</label>
<div class="control has-icons-left has-icons-right">
<input class="input is-success" type="text" placeholder="Text input" value="" hx-get="/users/username-check" hx-trigger="keyup changed delay:500ms" hx-target="#username-status">
<label>{{ string_dict['user_name'] }}</label>
<div class="control has-icons-left has-icons-right">
<input class="input is-success" type="text" placeholder="{{ string_dict['user_name'] }}" value="{% if initial_user['username'] %}{{ initial_user['username'] }}{% endif %}" name="username" hx-post="/users/user-check" hx-trigger="keyup changed delay:200ms" hx-trigger="load" hx-target="#username-status">
<span class="icon is-small is-left">
<i class="fas fa-user"></i>
</span>
<span class="icon is-small is-right">
<i class="fas fa-check"></i>
<i class="fa fa-user-circle-o" aria-hidden="true"></i>
</span>
</div>
<div id="username-status">
<p class="help is-success">{{ string_dict['user_name_available'] }}</p>
<p> {{ string_dict['user_name_hint'] }}</p>
</div>
</div>
<div class="field">
<label class="password">{{ string_dict['password'] }}</label>
<div class="control">
<input class="input" type="password" placeholder="{{ string_dict['password'] }}">
<div class="control has-icons-left has-icons-right">
<input class="input" name="password" type="password" placeholder="{{ string_dict['password'] }}" value="{% if initial_user['password'] %}{{ initial_user['password'] }}{% endif %}" hx-post="/users/user-check" hx-trigger="keyup changed delay:200ms" hx-target="#username-status">
<span class="icon is-small is-left">
<i class="fa fa-address-card" aria-hidden="true"></i>
</span>
</div>
</div>
<div class="field">
<label class="password">{{ string_dict['password_confirm'] }}</label>
<div class="control">
<input class="input" type="password" placeholder="{{ string_dict['password_confirm'] }}">
<div class="control has-icons-left has-icons-right">
<input class="input" name="password_confirm" type="password" placeholder="{{ string_dict['password_confirm'] }}" value="{% if initial_user['password_confirm'] %}{{ initial_user['password_confirm'] }}{% endif %}" hx-post="/users/user-check" hx-trigger="keyup changed delay:200ms" hx-target="#password-status">
<span class="icon is-small is-left">
<i class="fa fa-address-card" aria-hidden="true"></i>
</span>
</div>
</div>
</form>
</div> <!-- style 180 -->
</div>

View File

@@ -11,7 +11,8 @@ def set_language(LANG):
"""
Sets language for this applet
"""
translations = gettext.translation("welcome", './applets/welcome/locales', fallback=True, languages=[LANG])
translations = gettext.translation("welcome", './applets/welcome/locales',
fallback=True, languages=[LANG])
translations.install()
_ = translations.gettext
@@ -33,10 +34,12 @@ def welcome_index():
print(request.form["keyboard"])
if "timezone" in request.form.keys():
print(request.form["timezone"])
global lang
lang = current_app.config['CONFIG']['settings']['language']
blkid = lsblk.list_scsi_devices()
string_dict = build_stringlist()
build_menu()
build_summary()
menu = current_app.config['CONFIG']['settings']['menu_order'].split(" ")
next_step_url = menu[menu.index("welcome")+1]
previous_step_url = menu[menu.index("welcome")-1]
@@ -73,6 +76,7 @@ def build_stringlist():
string_dict['language_text'] = _("Language:")
string_dict['keylayout_text'] = _("Keyboard Layout:")
string_dict['timezone_text'] = _("Time Zone:")
string_dict['basicsettings_text'] = _("Basic Settings:")
lang_dict = {}
lang_dict['af'] = _("Afrikaans")
lang_dict['en'] = _("English (International)")
@@ -88,9 +92,21 @@ def build_menu():
"""
current_app.config['CONFIG']['settings']['menu']['welcome'] = (build_stringlist()['menu_item'], "/welcome", 10)
def build_summary():
"""
Write up a summary of what this module will do.
"""
html = ("<b><big>Basic settings</big></b><p> Language: " + lang + "</p>")
if not 'Summary' in current_app.config['CONFIG']:
current_app.config['CONFIG']['Summary'] = {}
current_app.config['CONFIG']['Summary']['welcome'] = {}
current_app.config['CONFIG']['Summary']['welcome']['heading'] = "Basic Settings"
print(current_app.config['CONFIG']['Summary']['welcome'])
text = "Language: " + str(current_app.config['CONFIG']['settings']['language'])
current_app.config['CONFIG']['Summary']['welcome']['text'] = text
return(html)
set_language(current_app.config['CONFIG']['settings']['language'])
build_menu
#for app in current_app.config['CONFIG']['settings']['apps'].split():
#print(app)
#current_app.config['CONFIG']['settings']['menu'][app] = (globals[app].build_stringlist()['menu_item'], "/" + app, 10)

View File

@@ -1,7 +1,9 @@
{% extends "layout.html" %}
{% block body %}
<img style="width: 100%; border-radius: 8px;" src="/static/applets/welcome/img/banner.png" alt="Debian Image banner" />
<img style="width: 100%; border-radius: 8px;"
src="/static/applets/welcome/img/banner.png"
alt="Debian Image banner" />
<p><b> {{ string_dict['welcome_text'] }} </b></p>
<p>{{ string_dict['confirm_text'] }} </p> <br />
@@ -9,7 +11,7 @@
<div style="padding-left: 15px;">
<form action="/welcome" method="POST">
<img src="/static/icons/keyboard.svg" /> {{ string_dict['language_text'] }}
<i class="fa fa-language" aria-hidden="true"></i> {{ string_dict['language_text'] }}
<div class="control is-link" width="180px">
<div class="select">
<select hx-post="/welcome" hx-target="body" name="lang" style="width: 220px">
@@ -23,7 +25,7 @@
<br>
<form>
<img src="/static/icons/keyboard.svg" /> {{ string_dict['keylayout_text'] }}
<i class="fa fa-keyboard-o" aria-hidden="true"></i> {{ string_dict['keylayout_text'] }}
<div class="control is-link">
<div class="select">
<select hx-post="/welcome" hx-target="body" name="keyboard" style="width: 220px">
@@ -36,7 +38,7 @@
<br />
<form>
<img src="/static/icons/keyboard.svg" /> {{ string_dict['timezone_text'] }}
<i class="fa fa-clock-o" aria-hidden="true"></i> {{ string_dict['timezone_text'] }}
<div class="control is-link">
<div class="select">
<select hx-post="/welcome" hx-target="body" name="timezone" style="width: 220px">

View File

@@ -11,8 +11,22 @@ settings:
menu_order: welcome users disks software summary
recipe:
Install some packages:
installer_dependencies:
description: Installing Dependencies
module: aptpkg
function: install
function: install
packagooes: apt
chrooti: /tmp
users:
module: users
function: add
users:
- username:
password:
fullname:
sudo: True
popcon:
module: software
function: popcon
enable_popcon: False

View File

@@ -9,11 +9,6 @@
{{ bottom_menu | safe }}
{% endif %}
{% if connect %}
<button class="button is-light">Connect Active Directory</button>
<button class="button is-light">Connect LDAP</button>
{% endif %}
</div>
@@ -21,9 +16,9 @@
<div style="position: absolute; bottom: 15px; right: 15px;">
{% if next_step %}
{% if next_step == "install" %}
<a href="/{{ next_step }}"><button class="button is-link">Install</button></a>
<a href="/{{ next_step }}"><button hx-trigger="click, keyup[shiftKey&&key=='I'] from:body" class="button is-link">Install</button></a>
{% else %}
<a href="/{{ next_step }}"><button class="button is-link">Next</button></a>
<a href="/{{ next_step }}"><button hx-trigger="click, keyup[shiftKey&&key=='N'] from:body" hx-target="/{{ next_step }}" class="button is-link">Next</button></a>
{% endif %}
{% endif %}
</div>

View File

@@ -6,6 +6,8 @@
<link href="/static/bulma.css" rel="stylesheet">
<link href="/static/system-installer.css" rel="stylesheet">
<link rel="shortcut icon" href="/static/icons/blippie.png" type="image/x-icon">
<link rel="stylesheet" href="/usr/share/fonts-fork-awesome/css/fork-awesome.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.2.0/css/fork-awesome.min.css" integrity="sha256-XoaMnoYC5TH6/+ihMEnospgm0J1PM/nioxbOUdnM8HY=" crossorigin="anonymous">
<script src="/static/htmx.min.js" crossorigin="anonymous"></script>
</head>
<body>