Add browser UI for todo testing
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
Todo Back is a deliberately small Rails JSON API packaged as a native StartOS 0.4 service. Its purpose is to validate the complete local-build → `.s9pk` → sideload workflow before investing in a larger application.
|
||||
|
||||
The interface root also serves a dependency-free browser page for quickly exercising every todo operation.
|
||||
|
||||
## API
|
||||
|
||||
- `GET /up` — Rails and PostgreSQL health check
|
||||
|
||||
@@ -4,6 +4,7 @@ Rails.application.configure do
|
||||
config.enable_reloading = false
|
||||
config.eager_load = true
|
||||
config.consider_all_requests_local = false
|
||||
config.public_file_server.enabled = true
|
||||
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
|
||||
config.log_tags = [:request_id]
|
||||
config.logger = ActiveSupport::TaggedLogging.logger($stdout)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Todo Back</title>
|
||||
<style>
|
||||
:root { color-scheme: light dark; font-family: system-ui, sans-serif; }
|
||||
body { max-width: 680px; margin: 3rem auto; padding: 0 1rem; }
|
||||
form, li { display: flex; gap: .6rem; align-items: center; }
|
||||
input[type="text"] { flex: 1; padding: .7rem; }
|
||||
button { padding: .6rem .8rem; cursor: pointer; }
|
||||
ul { list-style: none; padding: 0; }
|
||||
li { padding: .7rem 0; border-bottom: 1px solid #8886; }
|
||||
li span { flex: 1; }
|
||||
li.completed span { text-decoration: line-through; opacity: .6; }
|
||||
#status { min-height: 1.5rem; color: #d55; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Todo Back</h1>
|
||||
|
||||
<form id="new-todo">
|
||||
<input id="title" type="text" maxlength="255" placeholder="What needs doing?" required autofocus>
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
|
||||
<p id="status" role="status"></p>
|
||||
<ul id="todos"></ul>
|
||||
|
||||
<script>
|
||||
const list = document.querySelector('#todos')
|
||||
const form = document.querySelector('#new-todo')
|
||||
const title = document.querySelector('#title')
|
||||
const status = document.querySelector('#status')
|
||||
|
||||
async function api(path = '', options = {}) {
|
||||
const response = await fetch(`/todos${path}`, {
|
||||
headers: { 'Content-Type': 'application/json', ...options.headers },
|
||||
...options,
|
||||
})
|
||||
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`)
|
||||
return response.status === 204 ? null : response.json()
|
||||
}
|
||||
|
||||
function render(todos) {
|
||||
list.replaceChildren(...todos.map(todo => {
|
||||
const item = document.createElement('li')
|
||||
item.className = todo.completed ? 'completed' : ''
|
||||
|
||||
const checkbox = document.createElement('input')
|
||||
checkbox.type = 'checkbox'
|
||||
checkbox.checked = todo.completed
|
||||
checkbox.title = 'Mark complete'
|
||||
checkbox.addEventListener('change', async () => {
|
||||
await update(todo.id, { completed: checkbox.checked })
|
||||
})
|
||||
|
||||
const text = document.createElement('span')
|
||||
text.textContent = todo.title
|
||||
|
||||
const edit = document.createElement('button')
|
||||
edit.textContent = 'Edit'
|
||||
edit.addEventListener('click', async () => {
|
||||
const nextTitle = prompt('Todo title', todo.title)
|
||||
if (nextTitle?.trim()) await update(todo.id, { title: nextTitle.trim() })
|
||||
})
|
||||
|
||||
const remove = document.createElement('button')
|
||||
remove.textContent = 'Delete'
|
||||
remove.addEventListener('click', async () => {
|
||||
if (!confirm(`Delete “${todo.title}”?`)) return
|
||||
await run(() => api(`/${todo.id}`, { method: 'DELETE' }))
|
||||
})
|
||||
|
||||
item.append(checkbox, text, edit, remove)
|
||||
return item
|
||||
}))
|
||||
}
|
||||
|
||||
async function load() {
|
||||
render(await api())
|
||||
}
|
||||
|
||||
async function run(action) {
|
||||
status.textContent = ''
|
||||
try {
|
||||
await action()
|
||||
await load()
|
||||
} catch (error) {
|
||||
status.textContent = `Request failed: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
async function update(id, attributes) {
|
||||
await run(() => api(`/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ todo: attributes }),
|
||||
}))
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async event => {
|
||||
event.preventDefault()
|
||||
const value = title.value.trim()
|
||||
if (!value) return
|
||||
await run(() => api('', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ todo: { title: value, completed: false } }),
|
||||
}))
|
||||
title.value = ''
|
||||
title.focus()
|
||||
})
|
||||
|
||||
load().catch(error => {
|
||||
status.textContent = `Request failed: ${error.message}`
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +1,13 @@
|
||||
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
|
||||
|
||||
export const current = VersionInfo.of({
|
||||
version: '0.1.1:0',
|
||||
version: '0.1.2:0',
|
||||
releaseNotes: {
|
||||
en_US: 'Fix PostgreSQL readiness checks and HTTP/1.1 proxying; health now verifies the database.',
|
||||
es_ES: 'Corrige las comprobaciones de PostgreSQL y el proxy HTTP/1.1; la salud ahora verifica la base de datos.',
|
||||
de_DE: 'Korrigiert PostgreSQL-Prüfungen und HTTP/1.1-Proxying; der Status prüft nun die Datenbank.',
|
||||
pl_PL: 'Poprawia test PostgreSQL i proxy HTTP/1.1; kontrola stanu sprawdza teraz bazę danych.',
|
||||
fr_FR: 'Corrige les contrôles PostgreSQL et le proxy HTTP/1.1 ; la santé vérifie désormais la base de données.',
|
||||
en_US: 'Add a small browser interface for testing all todo operations.',
|
||||
es_ES: 'Agrega una pequeña interfaz web para probar todas las operaciones de tareas.',
|
||||
de_DE: 'Fügt eine kleine Browseroberfläche zum Testen aller Aufgabenoperationen hinzu.',
|
||||
pl_PL: 'Dodaje mały interfejs przeglądarkowy do testowania wszystkich operacji na zadaniach.',
|
||||
fr_FR: 'Ajoute une petite interface web pour tester toutes les opérations sur les tâches.',
|
||||
},
|
||||
migrations: {
|
||||
up: async () => {},
|
||||
|
||||
Reference in New Issue
Block a user