rewritten after codex recommendations

This commit is contained in:
2026-07-18 20:27:19 +02:00
parent f9dc885937
commit 399f539b4f
22 changed files with 1448 additions and 996 deletions
+27 -65
View File
@@ -1,75 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0" />
<title>{% block title %}Flowers{% endblock %}</title>
<!-- link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css" -->
<link rel="stylesheet" href="static/pure-min.css">
<link rel="stylesheet" href="static/grids-responsive-min.css">
<link rel="stylesheet" href="static/font-awesome.css">
<link rel=stylesheet type=text/css href="static/marketing.css">
<link rel=stylesheet type=text/css href="static/flower.css">
<script type="text/javascript" src="static/jquery-min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>{% block title %}Flowers{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('flower_vase.static', filename='flower.css') }}">
</head>
<body>
<header class="site-header">
<a class="brand" href="{{ url_for('flower_vase.index') }}" aria-label="Flowers home">
<span class="brand-mark" aria-hidden="true"></span>
<span>Flowers</span>
</a>
<nav class="nav-actions" aria-label="Vault actions">
{% block menuoptions %}{% endblock %}
</nav>
</header>
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
<ul class="pure-menu-list">
{% block menuoptions %}
<input type="text" id="search" placeholder="search..." value="{{filter}}">
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('new')" class="pure-menu-link">New</a></li>
<li class="pure-menu-item"><a href="javascript:DoSubmit('logout')" class="pure-menu-link">Logout</a></li>
<li class="pure-menu-item"><a href="javascript:DoSubmit('rehush')" class="pure-menu-link">Password</a></li>
{% endblock menuoptions %}
</ul>
</div>
</div>
{% endblock headandmenu %}
{% block content %}
{% endblock %}
<form id="gotopage" action="{{ url_for('flower_vase.application') }}" method="POST">
<input type="hidden" id="_action" name="action" value="">
<input type="hidden" id="_organization" name="organization" value="">
<input type="hidden" id="_myid" name="myid" value="">
<input type="hidden" id="_datetime" name="datetime" value="">
<input type="hidden" id="_filter" name="filter" value="">
</form>
<script language="javascript">
function DoSubmit(action, org, datetime, filter) {
$('#_action').val(action);
$('#_organization').val(org);
$('#_datetime').val(datetime);
$('#_filter').val( $('#search').val() );
$('#gotopage').submit();
}
</script>
{% block footer %}
<div class="footer l-box is-center">
Thank you for using the flowershop.
</div>
{% endblock %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="notices" aria-live="polite">
{% for category, message in messages %}
<div class="notice notice-{{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<main class="page-shell">
{% block content %}{% endblock %}
</main>
<footer>Private by design. Stored in your existing Flowers vault.</footer>
</body>
</html>
+21 -33
View File
@@ -1,35 +1,23 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Create a vault · Flowers{% endblock %}
{% block menuoptions %}<a class="text-link" href="{{ url_for('flower_vase.index') }}">Sign in</a>{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.create') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input maxlength="32" name="name" type="text" placeholder="make up your username">
</div>
<div class="pure-control-group">
<input maxlength="10" name="password" type="password" placeholder="make up you password, the longer the better">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Create Vase</button>
</div>
</fieldset>
</form>
{{ message }}
</div>
</div>
{% endblock content %}
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">A vase of your own</p>
<h1>Create a vault.</h1>
<p>This uses the existing Flowers database structure so it remains readable by compatible installations.</p>
</div>
<form class="card form-card" action="{{ url_for('flower_vase.create') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h2>New vault</h2>
{% if message %}<p class="field-error">{{ message }}</p>{% endif %}
<label for="name">Username</label>
<input maxlength="31" id="name" name="name" type="text" pattern="[A-Za-z0-9_]+" autocomplete="username" required>
<label for="password">Password</label>
<input maxlength="43" id="password" name="password" type="password" pattern="[A-Za-z0-9_+/-]+" autocomplete="new-password" required>
<p class="field-help">Use Base64-compatible characters for legacy encryption compatibility.</p>
<button class="button primary" type="submit">Create vault</button>
</form>
</section>
{% endblock %}
+38 -42
View File
@@ -1,45 +1,41 @@
{% extends "common.html" %}
{% block title %}{% if flower.organization %}Edit{% else %}New entry{% endif %} · Flowers{% endblock %}
{% block menuoptions %}
<li class="pure-menu-item pure-menu-selected"><a href="javascript:$('#newFlower').submit()" class="pure-menu-link">Save</a></li>
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('deactivate','{{ flower.organization }}','{{ flower.dateCreated }}','')" class="pure-menu-link">Delete</a></li>
<li class="pure-menu-item pure-menu-selected"><a href="javascript:DoSubmit('list','','','')" class="pure-menu-link">Cancel</a></li>
{% endblock menuoptions %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-stacked" id="newFlower" action="{{ url_for('flower_vase.application') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input maxlength="70" size="50" id="organization" name="organization" type="text" value="{{ flower.organization }}" placeholder="Organization">
</div>
<div class="pure-control-group">
<input maxlength="70" id="myname" name="myname" type="text" value="{{ flower.myName }}" placeholder="Your name (optional)">
</div>
<div class="pure-control-group">
<input maxlength="70" id="myid" name="myid" type="text" value="{{ flower.myID }}" placeholder="Your Login">
</div>
<div class="pure-control-group">
<input maxlength="30" id="secret" name="secret" type="text" placeholder="Your secret">
</div>
<input type="hidden" id="action" name="action" value="save">
<input type="hidden" id="filter" name="filter" value="DoNotSetFilterinCookie">
</fieldset>
</form>
</div>
</div>
<script language="javascript">
</script>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="list">
<button class="button ghost compact" type="submit">Cancel</button>
</form>
{% endblock %}
{% block content %}
<section class="editor-layout">
<div>
<p class="eyebrow">{% if flower.organization %}New version{% else %}New credential{% endif %}</p>
<h1>{% if flower.organization %}Edit entry{% else %}Add an entry{% endif %}</h1>
<p class="muted">Saving creates a new version. Your historical rows remain untouched.</p>
</div>
<form class="card form-card wide" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="save">
<input type="hidden" name="filter" value="{{ filter|default('') }}">
<label for="organization">Organization</label>
<input maxlength="80" id="organization" name="organization" type="text" value="{{ flower.organization }}" autocomplete="organization" required>
<label for="myname">Your name <span>optional</span></label>
<input maxlength="70" id="myname" name="myname" type="text" value="{{ flower.myName }}" autocomplete="name">
<label for="myid">Login</label>
<input maxlength="70" id="myid" name="myid" type="text" value="{{ flower.myID }}" autocomplete="username" required>
<label for="secret">Secret</label>
<input maxlength="120" id="secret" name="secret" type="password" autocomplete="new-password" required>
<button class="button primary" type="submit">Save new version</button>
</form>
{% if flower.dateCreated %}
<form class="danger-zone" action="{{ url_for('flower_vase.application') }}" method="post" onsubmit="return confirm('Remove this entry from the active list?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="deactivate">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<button class="button danger" type="submit">Deactivate entry</button>
</form>
{% endif %}
</section>
{% endblock %}
+22 -35
View File
@@ -1,37 +1,24 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Sign in · Flowers{% endblock %}
{% block menuoptions %}
<a class="text-link" href="{{ url_for('flower_vase.create') }}">Create a vault</a>
{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.application') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input name="name" type="text" placeholder="{{ name_suggestion }}">
</div>
<div class="pure-control-group">
<input name="password" type="password" placeholder="{{ pwd_suggestion }}">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Submit</button>
</div>
</fieldset>
<input type="hidden" id="action" name="action" value="login">
<input type="hidden" id="filter" name="filter" value="DoNotSetFilterinCookie">
</form>
</div>
</div>
{% endblock content %}
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">Your private collection</p>
<h1>Credentials, kept close.</h1>
<p>Open your existing vault. Your encrypted records stay in the same database and format.</p>
</div>
<form class="card form-card" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="login">
<h2>Sign in</h2>
<label for="name">Username</label>
<input id="name" name="name" type="text" autocomplete="username" placeholder="{{ name_suggestion }}" required autofocus>
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" placeholder="{{ pwd_suggestion }}" required>
<button class="button primary" type="submit">Open vault</button>
</form>
</section>
{% endblock %}
+72 -45
View File
@@ -1,51 +1,78 @@
{% extends "common.html" %}
{% block title %}Your vault · Flowers{% endblock %}
{% block menuoptions %}
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="new">
<button class="button primary compact" type="submit">New entry</button>
</form>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="rehush">
<button class="button ghost compact" type="submit">Password</button>
</form>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="logout">
<button class="button ghost compact" type="submit">Sign out</button>
</form>
{% endblock %}
{% block content %}
<section class="vault-header">
<div>
<p class="eyebrow">Active entries</p>
<h1>Your vault</h1>
</div>
<label class="search-field" for="search">
<span class="sr-only">Search entries</span>
<input id="search" type="search" placeholder="Search organization or login" value="{{ filter|default('') }}" autocomplete="off">
</label>
</section>
<div class="content-wrapper">
<div class="content">
<div class="pure-g">
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-3-5">
<table id="flowerlist" class="flowerlist pure-table pure-table-horizontal">
{% for flower in flowers %}
<tr class="flowerlist">
<td class="flowerlist">
<a href="javascript:DoSubmit('show', '{{flower.organization}}', '{{flower.dateCreated}}' , '')">
{{ flower.organization }}, {{ flower.myID }}
</a>
</td>
<td class="right"><span style="white-space: nowrap;">{{ flower.dateCreated[:10] }}</span></td>
</tr>
{% endfor %}
</table>
</div>
<div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-5">
</div>
</div>
</div>
<div class="entry-list" id="entry-list">
{% for flower in flowers %}
<form class="entry-row" data-search="{{ flower.organization }} {{ flower.myID }}" action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="show">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<input type="hidden" name="filter" class="current-filter" value="{{ filter|default('') }}">
<button type="submit" class="entry-button">
<span class="entry-icon" aria-hidden="true">{{ flower.organization[:1]|upper }}</span>
<span class="entry-copy">
<strong>{{ flower.organization }}</strong>
<span>{{ flower.myID }}</span>
</span>
<time datetime="{{ flower.dateCreated }}">{{ flower.dateCreated[:10] }}</time>
<span class="chevron" aria-hidden="true"></span>
</button>
</form>
{% else %}
<div class="empty-state">
<span aria-hidden="true"></span>
<h2>No active entries</h2>
<p>Create an entry to start your collection.</p>
</div>
{% endfor %}
</div>
<p class="empty-state filtered-empty" id="filtered-empty" hidden>No entries match that search.</p>
<script language="javascript">
var $rows = $('#flowerlist tr');
$('#search').keyup(function() {
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function() {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
});
$('#search').keyup();
<script>
const search = document.querySelector('#search');
const rows = [...document.querySelectorAll('.entry-row')];
const filteredEmpty = document.querySelector('#filtered-empty');
function filterRows() {
const query = search.value.trim().toLocaleLowerCase();
let visible = 0;
rows.forEach((row) => {
const match = row.dataset.search.toLocaleLowerCase().includes(query);
row.hidden = !match;
row.querySelector('.current-filter').value = search.value;
if (match) visible += 1;
});
filteredEmpty.hidden = visible !== 0 || rows.length === 0;
}
search.addEventListener('input', filterRows);
filterRows();
</script>
{% endblock content %}
{% endblock %}
+22 -38
View File
@@ -1,40 +1,24 @@
{% extends "common.html" %}
{% block headandmenu %}
<div class="header">
<div class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<a class="pure-menu-heading" href="">Flowers</a>
</div>
</div>
{% endblock headandmenu %}
{% block title %}Change password · Flowers{% endblock %}
{% block menuoptions %}<a class="text-link" href="{{ url_for('flower_vase.index') }}">Back to sign in</a>{% endblock %}
{% block content %}
<div class="splash-container">
<div class="splash">
<form class="pure-form pure-form-aligned" action="{{ url_for('flower_vase.update_pwd') }}" method="post">
<fieldset>
<div class="pure-control-group">
<input name="name" type="text" placeholder="your existing name">
</div>
<div class="pure-control-group">
<input name="old_password" type="password" placeholder="your existing password">
</div>
<div class="pure-control-group">
<input maxlength="10" name="new_password" type="password" placeholder="make up you password, the longer the better">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Update Password</button>
</div>
</fieldset>
</form>
{{ message }}
</div>
</div>
{% endblock content %}
<section class="auth-layout">
<div class="intro">
<p class="eyebrow">Re-encrypt your vault</p>
<h1>Change password.</h1>
<p>Every existing encrypted field will be rewritten with the new legacy-compatible key.</p>
</div>
<form class="card form-card" action="{{ url_for('flower_vase.update_pwd') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<h2>Vault credentials</h2>
{% if message %}<p class="field-error">{{ message }}</p>{% endif %}
<label for="name">Username</label>
<input id="name" name="name" type="text" autocomplete="username" required>
<label for="old-password">Current password</label>
<input id="old-password" name="old_password" type="password" autocomplete="current-password" required>
<label for="new-password">New password</label>
<input maxlength="43" id="new-password" name="new_password" type="password" pattern="[A-Za-z0-9_+/-]+" autocomplete="new-password" required>
<button class="button primary" type="submit">Re-encrypt vault</button>
</form>
</section>
{% endblock %}
+58 -44
View File
@@ -1,47 +1,61 @@
{% extends "common.html" %}
{% block title %}{{ flower.organization }} · Flowers{% endblock %}
{% block menuoptions %}
<li class="pure-menu-item pure-menu-selected"><a href="javascript:editFlower()" class="pure-menu-link">Edit</a></li>
{% endblock menuoptions %}
{% block content %}
<div class="splash-container">
<div class="splash">
<table class="pure-table" style="width: 100%">
<tr><td class="white right">Org</td><td class="white left">{{ flower.organization }}</td> </tr>
<tr><td class="white right">You</td><td class="white left">{{ flower.myName }}</td> </tr>
<tr><td class="white right">Login</td><td class="white left">{{ flower.myID }}</td> </tr>
<tr><td class="white right">Hash</td><td id="secrettd" class="white left"><div id="secret" value="{{ flower.mySecret }}" onclick="myCopyFunction()">{{ flower.mySecret }}</div></td></tr>
<tr><td class="white">{{ flower.dateCreated[:10] }}</td><td></td> </tr>
</table>
</div>
</div>
<script language="javascript">
setTimeout(function(){
DoSubmit('list', '{{flower.organization}}', '{{flower.dateCreated}}', 'DoNotSetFilterinCookie')
}, 5000);
function editFlower() {
DoSubmit('edit', '{{flower.organization}}', '{{flower.dateCreated}}', 'DoNotSetFilterinCookie')
}
function myCopyFunction() {
var copyText = document.getElementById("secret");
// Copy the text inside the text field
navigator.clipboard.writeText(copyText.getAttribute("value"));
// alert("Copied the text: "+copyText.getAttribute("value"));
$("#secrettd").removeClass("white");
$("#secrettd").addClass("blue");
}
</script>
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="list">
<input type="hidden" name="filter" value="{{ filter|default('') }}">
<button class="button ghost compact" type="submit">Back to vault</button>
</form>
{% endblock %}
{% block content %}
<section class="detail-card card">
<div class="detail-heading">
<span class="entry-icon large" aria-hidden="true">{{ flower.organization[:1]|upper }}</span>
<div>
<p class="eyebrow">Credential</p>
<h1>{{ flower.organization }}</h1>
{% if flower.dateCreated not in ('STORED', 'FAILED') %}<p class="muted">Version from {{ flower.dateCreated[:10] }}</p>{% endif %}
</div>
</div>
<dl class="details">
<div><dt>Name</dt><dd>{{ flower.myName or '—' }}</dd></div>
<div><dt>Login</dt><dd>{{ flower.myID }}</dd></div>
<div>
<dt>Secret</dt>
<dd class="secret-line">
<span id="secret" data-secret="{{ flower.mySecret }}">••••••••••••</span>
<button class="icon-button" id="reveal" type="button">Reveal</button>
<button class="icon-button" id="copy" type="button">Copy</button>
</dd>
</div>
</dl>
{% if flower.dateCreated not in ('STORED', 'FAILED') %}
<div class="detail-actions">
<form action="{{ url_for('flower_vase.application') }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="organization" value="{{ flower.organization }}">
<input type="hidden" name="datetime" value="{{ flower.dateCreated }}">
<button class="button primary" type="submit">Edit entry</button>
</form>
</div>
{% endif %}
</section>
<script>
const secret = document.querySelector('#secret');
const reveal = document.querySelector('#reveal');
const copy = document.querySelector('#copy');
let visible = false;
reveal.addEventListener('click', () => {
visible = !visible;
secret.textContent = visible ? secret.dataset.secret : '••••••••••••';
reveal.textContent = visible ? 'Hide' : 'Reveal';
});
copy.addEventListener('click', async () => {
await navigator.clipboard.writeText(secret.dataset.secret);
copy.textContent = 'Copied';
window.setTimeout(() => copy.textContent = 'Copy', 1400);
});
</script>
{% endblock %}