Files
ValidBoard/views/login.erb
T
Tom FluxandClaude Opus 5 2ae11e8d1e Add a Sinatra server that keeps boards in SQLite
Nullboard's storage layer bottoms out in getItem/setItem/delItem over
string keys, with boards, revisions and undo history all modelled on top
of them client-side. So the server needs to be nothing more than a
key/value store, and it doesn't parse a board anywhere.

One password, bcrypt-hashed, in the same database as the boards. The
session secret lives there too, so restarting the service doesn't sign
you out and the secret never has to exist in the unit file or in git.

Until a password is set every route returns 503 pointing at
bin/validboard-passwd. A first-run setup page would be friendlier, but it
would also hand the board to whoever found the URL first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU1vyTHj3uE9PJYSxRxwkU
2026-08-14 22:40:51 +01:00

124 lines
2.4 KiB
ERB

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ValidBoard</title>
<link rel="icon" href="/extras/favicon-16.png" sizes="16x16" type="image/png">
<style>
/* Barlow at nullboard's own sizes, so the login doesn't feel bolted on. */
@font-face {
font-family: 'f-barlow';
font-weight: 400;
font-style: normal;
src: url('/extras/Barlow-Regular.woff') format('woff');
}
@font-face {
font-family: 'f-barlow';
font-weight: 500;
font-style: normal;
src: url('/extras/Barlow-Medium.woff') format('woff');
}
html, body, input {
font-family: f-barlow, sans-serif;
font-size: 13px;
line-height: 17px;
padding: 0;
margin: 0;
}
body {
background: #f8f9fb;
color: #333;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.box {
width: 260px;
margin-top: -60px; /* sit a little above centre */
padding: 0 20px 20px;
}
h1 {
font-size: 18px;
font-weight: 500;
line-height: 24px;
margin: 0 0 2px;
color: #222;
}
.sub {
color: #999;
margin-bottom: 18px;
}
label {
display: block;
color: #999;
margin-bottom: 4px;
}
input[type=password] {
width: 100%;
box-sizing: border-box;
padding: 7px 9px;
border: 1px solid #dcdfe4;
border-radius: 3px;
background: #fff;
color: #333;
outline: none;
}
input[type=password]:focus {
border-color: #9fb4c7;
box-shadow: 0 0 0 2px rgba(159, 180, 199, .25);
}
button {
width: 100%;
margin-top: 12px;
padding: 8px;
border: 0;
border-radius: 3px;
background: #5c7a99;
color: #fff;
font-family: inherit;
font-size: 13px;
font-weight: 500;
cursor: pointer;
}
button:hover { background: #4e6b88; }
.error {
margin-bottom: 14px;
padding: 7px 9px;
border-radius: 3px;
background: #fdeaea;
color: #a33;
}
</style>
</head>
<body>
<div class="box">
<h1>ValidBoard</h1>
<div class="sub">Sign in to your boards.</div>
<% if error %>
<div class="error"><%= Rack::Utils.escape_html(error) %></div>
<% end %>
<form method="post" action="/login">
<label for="password">Password</label>
<input type="password" id="password" name="password" autofocus autocomplete="current-password">
<button type="submit">Sign in</button>
</form>
</div>
</body>
</html>